diff --git a/src/GlobalStates/GlobalStore.ts b/src/GlobalStates/GlobalStore.ts index bea8ab47..e73f23e0 100644 --- a/src/GlobalStates/GlobalStore.ts +++ b/src/GlobalStates/GlobalStore.ts @@ -17,6 +17,7 @@ interface Coord { type StoreState = { dataShape: number[]; + axisShape: number[]; shape: THREE.Vector3; valueScales: { maxVal: number; minVal: number }; colormap: THREE.DataTexture; @@ -27,6 +28,11 @@ type StoreState = { dimArrays: number[][]; dimNames: string[]; dimUnits: string[]; + axisDimArrays: number[][]; + axisDimNames: string[]; + axisDimUnits: string[]; + axisOrder: number[]; // Order of original axes for adjusting after transformation + axisFlipped: boolean[]; dimCoords: Record; plotDim: number; flipY:boolean; @@ -50,6 +56,7 @@ type StoreState = { // setters setDataShape: (dataShape: number[]) => void; + setAxisShape: (axisShape: number[]) => void; setShape: (shape: THREE.Vector3) => void; setValueScales: (valueScales: { maxVal: number; minVal: number }) => void; setColormap: (colormap: THREE.DataTexture) => void; @@ -61,6 +68,11 @@ type StoreState = { setDimArrays: (dimArrays: number[][]) => void; setDimNames: (dimNames: string[]) => void; setDimUnits: (dimUnits: string[]) => void; + setAxisDimArrays: (axisDimArrays: number[][]) => void; + setAxisDimNames: (axisDimNames: string[]) => void; + setAxisDimUnits: (axisDimUnits: string[]) => void; + setAxisOrder: (axisOrder: number[]) => void; + setAxisFlipped: (axisFlipped: boolean[]) => void; setDimCoords: (dimCoords?: Record) => void; updateDimCoords: (newDims: Record) => void; setPlotDim: (plotDim: number) => void; @@ -86,6 +98,7 @@ type StoreState = { export const useGlobalStore = create((set, get) => ({ dataShape: [1, 1, 1], + axisShape: [1, 1, 1], shape: new THREE.Vector3(2, 2, 2), valueScales: { maxVal: 1, minVal: -1 }, colormap: GetColorMapTexture(), @@ -96,6 +109,11 @@ export const useGlobalStore = create((set, get) => ({ dimArrays: [[0], [0], [0]], dimNames: ["Default"], dimUnits: ["Default"], + axisDimArrays: [[0], [0], [0]], + axisDimNames: ["Default"], + axisDimUnits: ["Default"], + axisOrder: [0,1,2], + axisFlipped: [false, false, false], dimCoords: {}, plotDim: 0, flipY: false, @@ -119,6 +137,7 @@ export const useGlobalStore = create((set, get) => ({ // setters setDataShape: (dataShape) => set({ dataShape }), + setAxisShape: (axisShape) => set({ axisShape }), setShape: (shape) => set({ shape }), setValueScales: (valueScales) => set({ valueScales }), setColormap: (colormap) => set({ colormap }), @@ -140,6 +159,11 @@ export const useGlobalStore = create((set, get) => ({ setDimArrays: (dimArrays) => set({ dimArrays }), setDimNames: (dimNames) => set({ dimNames }), setDimUnits: (dimUnits) => set({ dimUnits }), + setAxisDimArrays: (axisDimArrays) => set({ axisDimArrays }), + setAxisDimNames: (axisDimNames) => set({ axisDimNames }), + setAxisDimUnits: (axisDimUnits) => set({ axisDimUnits }), + setAxisOrder: (axisOrder) => set({ axisOrder }), + setAxisFlipped: (axisFlipped) => set({ axisFlipped }), setDimCoords: (dimCoords) => set({ dimCoords }), updateDimCoords: (newDims) => { const merged = { ...newDims,...get().dimCoords }; diff --git a/src/GlobalStates/PlotStore.ts b/src/GlobalStates/PlotStore.ts index 39219a62..81226e50 100644 --- a/src/GlobalStates/PlotStore.ts +++ b/src/GlobalStates/PlotStore.ts @@ -64,7 +64,8 @@ type PlotState ={ maskValue: number; cameraPosition: THREE.Vector3; disablePointScale: boolean; - camera: THREE.Camera | undefined; + permute: number[] | undefined; + showTransformAxis: boolean; setQuality: (quality: number) => void; setTimeScale: (timeScale : number) =>void; @@ -120,10 +121,8 @@ type PlotState ={ setUseOrtho: (useOrtho: boolean) => void; setFillValue: (fillValue: number | undefined) => void; setCameraPosition: (cameraPosition: THREE.Vector3) => void; - } - export const usePlotStore = create((set, get) => ({ plotType: "volume", pointSize: 5, @@ -188,7 +187,8 @@ export const usePlotStore = create((set, get) => ({ borderWidth: 0.05, cameraPosition: new THREE.Vector3(0, 0, 5), disablePointScale: false, - camera: undefined, + permute: undefined, + showTransformAxis: false, setVTransferRange: (vTransferRange) => set({ vTransferRange }), setVTransferScale: (vTransferScale) => set({ vTransferScale }), @@ -246,5 +246,4 @@ export const usePlotStore = create((set, get) => ({ setUseOrtho: (useOrtho) => set({ useOrtho }), setFillValue: (fillValue) => set({ fillValue }), setCameraPosition: (cameraPosition) => set({ cameraPosition }), - })) \ No newline at end of file diff --git a/src/GlobalStates/PlotTransformStore.ts b/src/GlobalStates/PlotTransformStore.ts new file mode 100644 index 00000000..b8325d4f --- /dev/null +++ b/src/GlobalStates/PlotTransformStore.ts @@ -0,0 +1,25 @@ +import { create } from "zustand"; + +type PlotTransformState = { + rotateZ: number; + rotateX: number; + mirrorVertical: boolean; + mirrorHorizontal: boolean; + + setRotateZ: (rotateZ: number) => void; + setRotateX: (rotateX: number) => void; + setMirrorVertical: (mirrorVertical: boolean) => void; + setMirrorHorizontal: (mirrorHorizontal: boolean) => void; +} + +export const usePlotTransformStore = create((set) => ({ + rotateZ: 0, + rotateX: 0, + mirrorVertical: false, + mirrorHorizontal: false, + + setRotateZ: (rotateZ) => set({ rotateZ }), + setRotateX: (rotateX) => set({ rotateX }), + setMirrorVertical: (mirrorVertical) => set({ mirrorVertical }), + setMirrorHorizontal: (mirrorHorizontal) => set({ mirrorHorizontal }), +})); \ No newline at end of file diff --git a/src/GlobalStates/ZarrStore.ts b/src/GlobalStates/ZarrStore.ts index 423796df..00755ee3 100644 --- a/src/GlobalStates/ZarrStore.ts +++ b/src/GlobalStates/ZarrStore.ts @@ -23,7 +23,6 @@ type ZarrState = { fetchOptions: FetchStoreOptions | null; abortController: AbortController | null; fetchKey: number; - blobKey: string | undefined; // The key for the stored File blob for a local NC setZSlice: (zSlice: [number , number | null]) => void; setYSlice: (ySlice: [number , number | null]) => void; @@ -64,7 +63,6 @@ export const useZarrStore = create((set, get) => ({ fetchOptions: null, abortController: null, fetchKey: 0, - blobKey: undefined, setZSlice: (zSlice) => set({ zSlice }), setYSlice: (ySlice) => set({ ySlice }), diff --git a/src/assets/imgs/SeasFire-logo.png b/src/assets/SeasFire-logo.png similarity index 100% rename from src/assets/imgs/SeasFire-logo.png rename to src/assets/SeasFire-logo.png diff --git a/src/assets/catalogs/IcechunkCatalog.ts b/src/assets/catalogs/IcechunkCatalog.ts deleted file mode 100644 index 5ba38e62..00000000 --- a/src/assets/catalogs/IcechunkCatalog.ts +++ /dev/null @@ -1,128 +0,0 @@ -export const ICECHUNK_CATALOG = [ - { - key: 'chirps-daily', - label: 'CHIRPS Daily Precipitation', - subtitle: '', - store: 'https://data.source.coop/e4drr-project/observations/chirps_daily_icechunk', - }, - { - key: 'earthmover-era5-surface', - label: 'Earthmover ERA5 Surface Reanalysis', - subtitle: 'Multi-Variable', - store: 'https://earthmover-icechunk-era5.s3.us-east-1.amazonaws.com/era5_surface_aws', - }, - { - key: 'noaa-gfs-forecast', - label: 'NOAA GFS', - subtitle: 'Global Weather Forecast', - store: 'https://dynamical-noaa-gfs.s3.us-west-2.amazonaws.com/noaa-gfs-forecast/v0.2.7.icechunk/', - }, - { - key: 'dwd-icon-eu-forecast', - label: 'DWD ICON-EU', - subtitle: 'Numerical Weather Forecast', - store: 'https://dynamical-dwd-icon-eu.s3.us-west-2.amazonaws.com/dwd-icon-eu-forecast-5-day/v0.2.0.icechunk/', - }, - { - key: 'ecmwf-aifs-single-forecast', - label: 'ECMWF AIFS Single Forecast', - subtitle: 'Artificial Intelligence Forecasting System', - store: 'https://dynamical-ecmwf-aifs-single.s3.us-west-2.amazonaws.com/ecmwf-aifs-single-forecast/v0.1.0.icechunk/', - }, - { - key: 'metoffice-global-wave', - label: 'UK Met Office Global Wave Forecast', - subtitle: '', - store: 'https://data.source.coop/bkr/metoffice/metoffice_global_wave.icechunk', - }, - { - key: 'metoffice-global-deterministic-6h', - label: 'UK Met Office Global Deterministic Atmosphere Forecast', - subtitle: '6-hourly 24hr', - store: 'https://data.source.coop/bkr/metoffice/metoffice_global_deterministic_10km_6hourly_24hr.icechunk', - }, - { - key: 'nasa-geos-15min', - label: 'NASA GEOS Atmospheric Reanalysis', - subtitle: '15-minute', - store: 'https://data.source.coop/bkr/geos/geos_15min.icechunk', - }, - { - key: 'metoffice-global-deterministic-11h', - label: 'UK Met Office Global Deterministic Atmosphere Forecast', - subtitle: '11-hour', - store: 'https://data.source.coop/bkr/metoffice/metoffice_global_deterministic_10km_11hour.icechunk', - }, - { - key: 'metoffice-global-deterministic', - label: 'UK Met Office Global Deterministic Atmosphere Forecast', - subtitle: '', - store: 'https://data.source.coop/bkr/metoffice/metoffice_global_deterministic_10km.icechunk', - }, - { - key: 'noaa-hrrr-analysis', - label: 'NOAA HRRR', - subtitle: 'High-Resolution Rapid Refresh Analysis', - store: 'https://dynamical-noaa-hrrr.s3.us-west-2.amazonaws.com/noaa-hrrr-analysis/v0.2.0.icechunk/', - }, - { - key: 'noaa-mrms-conus-hourly', - label: 'NOAA MRMS CONUS', - subtitle: 'Hourly Radar-Based Precipitation Analysis', - store: 'https://dynamical-noaa-mrms.s3.amazonaws.com/noaa-mrms-conus-analysis-hourly/v0.3.0.icechunk/', - }, - { - key: 'ecmwf-aifs-ens-forecast', - label: 'ECMWF AIFS Ensemble Forecast', - subtitle: '', - store: 'https://dynamical-ecmwf-aifs-ens.s3.us-west-2.amazonaws.com/ecmwf-aifs-ens-forecast/v0.1.0.icechunk/', - }, - { - key: 'ecmwf-ifs-ens-15day', - label: 'ECMWF IFS Ensemble 15-day Weather Forecast', - subtitle: '0.25°', - store: 'https://dynamical-ecmwf-ifs-ens.s3.us-west-2.amazonaws.com/ecmwf-ifs-ens-forecast-15-day-0-25-degree/v0.1.0.icechunk/', - }, - { - key: 'noaa-gefs-35day-forecast', - label: 'NOAA GEFS 35-day Ensemble Forecast', - subtitle: '', - store: 'https://dynamical-noaa-gefs.s3.us-west-2.amazonaws.com/noaa-gefs-forecast-35-day/v0.2.0.icechunk/', - }, - { - key: 'silam-aerosol', - label: 'SILAM Global Dust Model Forecasts', - subtitle: 'Aerosol', - store: 'https://data.source.coop/bkr/silam-dust/silam_aerosol.icechunk', - }, - { - key: 'cams-aerosol-analysis', - label: 'CAMS Aerosol Analysis', - subtitle: 'Copernicus CAMS Operational Archive', - store: 'https://data.source.coop/bkr/cams/cams.icechunk', - }, - { - key: 'cams-aerosol-forecast', - label: 'CAMS Aerosol Forecast', - subtitle: 'Copernicus CAMS Operational Archive', - store: 'https://data.source.coop/bkr/cams/cams_analysis_and_forecast.icechunk', - }, - { - key: 'nasa-rasi', - label: 'NASA RASI', - subtitle: 'Risk Analysis and Solutions Innovators', - store: 'https://nasa-waterinsight.s3.us-west-2.amazonaws.com/virtual-zarr-store/icechunk/RASI/HISTORICAL/', - }, - { - key: 'alaska-hrrr', - label: 'Alaska HRRR', - subtitle: '', - store: 'https://data.source.coop/bkr/dmi/alaska_hrrr.icechunk', - }, - { - key: 'ndvi-test', - label: 'NDVI', - subtitle: 'Normalized Difference Vegetation Index', - store: 'https://data.source.coop/eeholmes/chlaz/icechunk', - }, -]; \ No newline at end of file diff --git a/src/assets/catalogs/ZarrCatalog.ts b/src/assets/catalogs/ZarrCatalog.ts deleted file mode 100644 index 350d82d7..00000000 --- a/src/assets/catalogs/ZarrCatalog.ts +++ /dev/null @@ -1,92 +0,0 @@ -export const ZARR_CATALOG = [ - { - key: 'seasfire', - label: 'SeasFire Cube', - subtitle: 'A Global Dataset for Seasonal Fire Modeling in the Earth System', - store: 'https://s3.bgc-jena.mpg.de:9000/misc/seasfire_rechunked.zarr', - }, - { - key: 'ESDC', - label: 'ESDC', - subtitle: 'Earth System Data Cube v3.0.2', - store: 'https://s3.bgc-jena.mpg.de:9000/esdl-esdc-v3.0.2/esdc-16d-2.5deg-46x72x1440-3.0.2.zarr', - }, - { - key: 'precipitation', - label: 'Precipitation (EC-Earth3P-HR)', - subtitle: 'Precipitation data from EC-Earth3P-HR highresSST-present', - store: 'https://storage.googleapis.com/cmip6/CMIP6/HighResMIP/EC-Earth-Consortium/EC-Earth3P-HR/highresSST-present/r1i1p1f1/Amon/pr/gr/v20170811/', - }, - { - key: 'arco-ocean', - label: 'ARCO-OCEAN', - subtitle: 'Global Ocean Reanalysis Daily', - store: 'https://ogs-arco-ocean.s3.eu-south-1.amazonaws.com/dataset/tres=1d/res=0p25/levels=10/', - }, - { - key: 'cmip6-mpi-esm1-2-hr-tas', - label: 'CMIP6 MPI-ESM1-2-HR', - subtitle: 'Historical Near-Surface Air Temperature', - store: 'https://storage.googleapis.com/cmip6/CMIP6/CMIP/MPI-M/MPI-ESM1-2-HR/historical/r1i1p1f1/Amon/tas/gn/v20190710/', - }, - { - key: 'cmip6-pmip-ipsl-cm6a-lr-tas', - label: 'CMIP6 PMIP IPSL-CM6A-LR', - subtitle: 'Mid-Holocene Near-Surface Air Temperature', - store: 'https://storage.googleapis.com/cmip6/CMIP6/PMIP/IPSL/IPSL-CM6A-LR/midHolocene/r1i1p1f3/Amon/tas/gr/v20180926/', - }, - { - key: 'cmip6-hadgem3-gc31-hm-tas', - label: 'CMIP6 HighResMIP HadGEM3-GC31-HM', - subtitle: 'Near-Surface Air Temperature', - store: 'https://storage.googleapis.com/cmip6/CMIP6/HighResMIP/MOHC/HadGEM3-GC31-HM/highresSST-present/r1i1p1f1/Amon/tas/gn/v20170831/', - }, - { - key: 'indian-ocean-chl', - label: 'Indian Ocean Physical and Biological Variables', - subtitle: '', - store: 'https://storage.googleapis.com/nmfs_odp_nwfsc/CB/mind_the_chl_gap/IO.zarr', - }, - { - key: 'nasa-mur-sst', - label: 'NASA MUR Sea Surface Temperature', - subtitle: 'Multi-scale Ultra-high Resolution SST', - store: 'https://mur-sst.s3.us-west-2.amazonaws.com/zarr-v1/', - }, - { - key: 'cmip6-gfdl-esm4-tos', - label: 'CMIP6 NOAA GFDL-ESM4', - subtitle: 'Historical Sea Surface Temperature', - store: 'https://storage.googleapis.com/cmip6/CMIP6/CMIP/NOAA-GFDL/GFDL-ESM4/historical/r2i1p1f1/Omon/tos/gn/v20180701/', - }, - { - key: 'cmip6-awi-cm-1-1-mr-tos', - label: 'CMIP6 AWI-CM-1-1-MR', - subtitle: 'Historical Daily Sea Surface Temperature', - store: 'https://cmip6-pds.s3.amazonaws.com/CMIP6/CMIP/AWI/AWI-CM-1-1-MR/historical/r1i1p1f1/Oday/tos/gn/v20181218/', - }, - { - key: 'carbonplan-antarctic-era5', - label: 'CarbonPlan Antarctic ERA5 Reanalysis', - subtitle: '', - store: 'https://carbonplan-share.s3.us-west-2.amazonaws.com/zarr-layer-examples/antarctic_era5.zarr', - }, - { - key: 'silam-dust', - label: 'SILAM Global Dust Model Forecasts', - subtitle: 'Dust', - store: 'https://data.source.coop/bkr/silam-dust/silam_global_dust_v3.zarr', - }, - { - key: 'silam-dust-v2', - label: 'SILAM Global Dust Model Forecasts (v2)', - subtitle: 'Dust', - store: 'https://data.source.coop/bkr/silam-dust/data.zarr', - }, - { - key: 'gefs-35day-direct', - label: 'NOAA GEFS 35-day Ensemble Forecast', - subtitle: '', - store: 'https://data.dynamical.org/noaa/gefs/forecast-35-day/latest.zarr', - }, -]; \ No newline at end of file diff --git a/src/assets/index.ts b/src/assets/index.ts index 4c1130b4..fb9ba350 100644 --- a/src/assets/index.ts +++ b/src/assets/index.ts @@ -1,11 +1,9 @@ -import logoSeasFire from "./imgs/logoS.png"; -import logoSF from "./imgs/SeasFire-logo.png"; -import logo from "./imgs/logo.png"; -import logoBGC_MPI from "./imgs/logo_bgc_mpi.png" -import logoBGC from "./imgs/logo_bgc.png" -import logoMPI from "./imgs/logo_mpi.png" -import { ZARR_CATALOG } from "./catalogs/ZarrCatalog" -import { ICECHUNK_CATALOG } from "./catalogs/IcechunkCatalog" +import logoSeasFire from "./logoS.png"; +import logoSF from "./SeasFire-logo.png"; +import logo from "./logo.png"; +import logoBGC_MPI from "./logo_bgc_mpi.png" +import logoBGC from "./logo_bgc.png" +import logoMPI from "./logo_mpi.png" export { logoSeasFire, @@ -13,7 +11,5 @@ export { logoSF, logoBGC_MPI, logoBGC, - logoMPI, - ZARR_CATALOG, - ICECHUNK_CATALOG, + logoMPI }; \ No newline at end of file diff --git a/src/assets/imgs/line-graph.svg b/src/assets/line-graph.svg similarity index 100% rename from src/assets/imgs/line-graph.svg rename to src/assets/line-graph.svg diff --git a/src/assets/imgs/logo.png b/src/assets/logo.png similarity index 100% rename from src/assets/imgs/logo.png rename to src/assets/logo.png diff --git a/src/assets/imgs/logoS.png b/src/assets/logoS.png similarity index 100% rename from src/assets/imgs/logoS.png rename to src/assets/logoS.png diff --git a/src/assets/imgs/logo_bgc.png b/src/assets/logo_bgc.png similarity index 100% rename from src/assets/imgs/logo_bgc.png rename to src/assets/logo_bgc.png diff --git a/src/assets/imgs/logo_bgc_mpi.png b/src/assets/logo_bgc_mpi.png similarity index 100% rename from src/assets/imgs/logo_bgc_mpi.png rename to src/assets/logo_bgc_mpi.png diff --git a/src/assets/imgs/logo_mpi.png b/src/assets/logo_mpi.png similarity index 100% rename from src/assets/imgs/logo_mpi.png rename to src/assets/logo_mpi.png diff --git a/src/assets/imgs/logocut.png b/src/assets/logocut.png similarity index 100% rename from src/assets/imgs/logocut.png rename to src/assets/logocut.png diff --git a/src/assets/imgs/seasfire_logo.png b/src/assets/seasfire_logo.png similarity index 100% rename from src/assets/imgs/seasfire_logo.png rename to src/assets/seasfire_logo.png diff --git a/src/assets/imgs/settings.svg b/src/assets/settings.svg similarity index 100% rename from src/assets/imgs/settings.svg rename to src/assets/settings.svg diff --git a/src/components/StoreInitializer.tsx b/src/components/StoreInitializer.tsx index 3cf178fb..199308a4 100644 --- a/src/components/StoreInitializer.tsx +++ b/src/components/StoreInitializer.tsx @@ -3,12 +3,8 @@ import { useEffect, Suspense } from "react"; import { useSearchParams } from "next/navigation"; import { useGlobalStore } from "@/GlobalStates/GlobalStore"; import { useZarrStore } from '@/GlobalStates/ZarrStore'; -import { usePlotStore } from "@/GlobalStates/PlotStore"; import { useShallow } from 'zustand/shallow'; import { isRemoteStore } from '@/utils/isRemoteStore'; -import { loadNetCDF } from "@/utils/loadNetCDF"; -import { loadFile } from "@/utils/IndexDB"; -import { LoadLocalZarr } from "./ui/MainPanel/LocalZarr"; function StoreInitializerInner() { const searchParams = useSearchParams(); @@ -21,40 +17,11 @@ function StoreInitializerInner() { useEffect(() => { const store = searchParams.get("store"); - let data = searchParams.get("data") - if (data){ - try{ - const fullObj = JSON.parse(data); - if (fullObj.zarrState.blobKey){ // If NC local must load file beforehand - const blobKey = fullObj.zarrState.blobKey - const isNC = fullObj.zarrState.useNC - loadFile(blobKey).then(cache =>{ - if (!isNC){ - LoadLocalZarr(cache?.blob as File[]) - } else { - //@ts-ignore cache is what we want - const file = cache.blob as File - loadNetCDF(file, file.name).then(() => { - useZarrStore.setState(fullObj.zarrState); - useGlobalStore.setState(fullObj.globalState); - usePlotStore.setState(fullObj.plotState); - }) - } - }) - } else { - useZarrStore.setState(fullObj.zarrState) - useGlobalStore.setState(fullObj.globalState) - usePlotStore.setState(fullObj.plotState) - } - } catch { - console.error('Something Failed :/') - } - } if (!store) { setStoreFromURL(false); return; } - + const isNC = searchParams.get("format") === "nc"; setUseNC(isNC); setFetchNC(isNC); diff --git a/src/components/computation/WGSLShaders.ts b/src/components/computation/WGSLShaders.ts index c890fcaf..42339bf6 100644 --- a/src/components/computation/WGSLShaders.ts +++ b/src/components/computation/WGSLShaders.ts @@ -492,7 +492,6 @@ export const createShaders = (precision: Precision) => { @group(0) @binding(2) var outputData: array<${precision}>; @group(0) @binding(3) var params: Params; - ${isNaNFunc} @compute @workgroup_size(16, 16, 1) fn main(@builtin(global_invocation_id) global_id: vec3) { let zStride = params.zStride; @@ -513,8 +512,6 @@ export const createShaders = (precision: Precision) => { var ySum: f32 = 0; var xSum: f32 = 0.0; - var nanCount: u32 = 0; - // Iterate along the dimension we're averaging if (reduceDim == 0u) { let cCoord = outX * xStride + outY * yStride; @@ -523,7 +520,6 @@ export const createShaders = (precision: Precision) => { let xi: f32 = f32(firstData[inputIndex]); let yi: f32 = f32(secondData[inputIndex]); if (isNaN(xi) || isNaN(yi)){ - nanCount++; continue; } xSum += f32(firstData[inputIndex]); @@ -536,7 +532,6 @@ export const createShaders = (precision: Precision) => { let xi: f32 = f32(firstData[inputIndex]); let yi: f32 = f32(secondData[inputIndex]); if (isNaN(xi) || isNaN(yi)){ - nanCount++; continue; } xSum += f32(firstData[inputIndex]); @@ -549,7 +544,6 @@ export const createShaders = (precision: Precision) => { let xi: f32 = f32(firstData[inputIndex]); let yi: f32 = f32(secondData[inputIndex]); if (isNaN(xi) || isNaN(yi)){ - nanCount++; continue; } xSum += f32(firstData[inputIndex]); @@ -557,12 +551,11 @@ export const createShaders = (precision: Precision) => { } } - let xMean: f32 = xSum / f32(dimLength - nanCount); - let yMean: f32 = ySum / f32(dimLength - nanCount); + let xMean: f32 = xSum / f32(dimLength); + let yMean: f32 = ySum / f32(dimLength); var numSum: f32 = 0; var denomSum: f32 = 0; - if (reduceDim == 0u) { // Average along Z let cCoord = outX * xStride + outY * yStride; for (var z: u32 = 0u; z < dimLength; z++) { @@ -622,7 +615,6 @@ export const createShaders = (precision: Precision) => { @group(0) @binding(2) var outputData: array<${precision}>; @group(0) @binding(3) var params: Params; - ${isNaNFunc} @compute @workgroup_size(16, 16, 1) fn main(@builtin(global_invocation_id) global_id: vec3) { let zStride = params.zStride; @@ -658,25 +650,20 @@ export const createShaders = (precision: Precision) => { iterStride = xStride; } - var nanCount: u32 = 0; - // Single pass: calculate sums, means, and covariance for (var i: u32 = 0u; i < dimLength; i++) { let inputIndex = baseCoord + (i * iterStride); let xi: f32 = f32(firstData[inputIndex]); let yi: f32 = f32(secondData[inputIndex]); if (isNaN(xi) || isNaN(yi)){ - nanCount++; continue; } xSum += xi; ySum += yi; } - var N: f32 = f32(dimLength - nanCount); - - let xMean: f32 = xSum / N; - let yMean: f32 = ySum / N; + let xMean: f32 = xSum / f32(dimLength); + let yMean: f32 = ySum / f32(dimLength); // Second pass for covariance calculation for (var i: u32 = 0u; i < dimLength; i++) { @@ -690,7 +677,7 @@ export const createShaders = (precision: Precision) => { } let outputIndex = outY * xSize + outX; - outputData[outputIndex] = ${precision}(numSum / (N - 1)); + outputData[outputIndex] = ${precision}(numSum / (f32(dimLength) - 1)); } `, @@ -710,8 +697,6 @@ export const createShaders = (precision: Precision) => { @group(0) @binding(2) var outputData: array<${precision}>; @group(0) @binding(3) var params: Params; - ${isNaNFunc} - @compute @workgroup_size(16, 16, 1) fn main(@builtin(global_invocation_id) global_id: vec3) { let zStride = params.zStride; @@ -734,8 +719,6 @@ export const createShaders = (precision: Precision) => { var ySum: f32 = 0.0; var yySum: f32 = 0.0; var xySum: f32 = 0.0; - - var nanCount: u32 = 0; // Iterate along the dimension we're averaging if (reduceDim == 0u) { // Average along Z let cCoord = outX * xStride + outY * yStride; @@ -744,7 +727,6 @@ export const createShaders = (precision: Precision) => { let xI = f32(firstData[inputIndex]); let yI = f32(secondData[inputIndex]); if (isNaN(xI) || isNaN(yI)){ - nanCount++; continue; } xSum += xI; @@ -760,7 +742,6 @@ export const createShaders = (precision: Precision) => { let xI = f32(firstData[inputIndex]); let yI = f32(secondData[inputIndex]); if (isNaN(xI) || isNaN(yI)){ - nanCount++; continue; } xSum += xI; @@ -776,7 +757,6 @@ export const createShaders = (precision: Precision) => { let xI = f32(firstData[inputIndex]); let yI = f32(secondData[inputIndex]); if (isNaN(xI) || isNaN(yI)){ - nanCount++; continue; } xSum += xI; @@ -787,7 +767,7 @@ export const createShaders = (precision: Precision) => { } } - let N: f32 = f32(dimLength - nanCount); + let N: f32 = f32(dimLength); let meanX = xSum / N; let meanY = ySum / N; let varX = (xxSum / N) - (meanX * meanX); @@ -970,7 +950,6 @@ export const createShaders = (precision: Precision) => { @group(0) @binding(2) var outputData: array<${precision}>; @group(0) @binding(3) var params: Params; - ${isNaNFunc} @compute @workgroup_size(4, 4, 4) fn main(@builtin(global_invocation_id) global_id: vec3) { let zStride = params.zStride; @@ -1076,7 +1055,7 @@ export const createShaders = (precision: Precision) => { @group(0) @binding(1) var secondData: array<${precision}>; @group(0) @binding(2) var outputData: array<${precision}>; @group(0) @binding(3) var params: Params; - ${isNaNFunc} + @compute @workgroup_size(4, 4, 4) fn main(@builtin(global_invocation_id) global_id: vec3) { let zStride = params.zStride; @@ -1190,7 +1169,7 @@ export const createShaders = (precision: Precision) => { @group(0) @binding(1) var secondData: array<${precision}>; @group(0) @binding(2) var outputData: array<${precision}>; @group(0) @binding(3) var params: Params; - ${isNaNFunc} + @compute @workgroup_size(4, 4, 4) fn main(@builtin(global_invocation_id) global_id: vec3) { let zStride = params.zStride; @@ -1432,7 +1411,7 @@ export const createShaders = (precision: Precision) => { @group(0) @binding(0) var inputData: array<${precision}>; @group(0) @binding(1) var outputData: array; @group(0) @binding(2) var params: Params; - ${isNaNFunc} + @compute @workgroup_size(4, 4, 4) fn main(@builtin(global_invocation_id) global_id: vec3) { let zStride = params.zStride; diff --git a/src/components/plots/AxisLines.tsx b/src/components/plots/AxisLines.tsx index 86d9a67d..f1093f56 100644 --- a/src/components/plots/AxisLines.tsx +++ b/src/components/plots/AxisLines.tsx @@ -12,15 +12,16 @@ import { LineSegmentsGeometry } from 'three/addons/lines/LineSegmentsGeometry.js import { LineSegments2 } from 'three/addons/lines/LineSegments2.js'; import { LineMaterial } from 'three-stdlib'; import { useFrame } from '@react-three/fiber'; -import { parseLoc, coarsenFlatArray } from '@/utils/HelperFuncs'; +import { parseLoc, coarsenFlatArray, permuteArr } from '@/utils/HelperFuncs'; import { useCSSVariable } from '../ui'; import * as THREE from 'three' +import { usePlotTransformStore } from '@/GlobalStates/PlotTransformStore'; const AXIS_CONSTANTS = { INITIAL_RESOLUTION: 7, MAX_RESOLUTION: 20, MIN_RESOLUTION: 1, - PC_GLOBAL_SCALE_DIVISOR: 500, + PC_GLOBAL_SCALE_DIVISOR: 1000, LINE_WIDTH: 2.0, TICK_LENGTH_FACTOR: 0.05, TICK_FONT_SIZE_FACTOR: 0.05, @@ -32,55 +33,67 @@ const AXIS_CONSTANTS = { }; const CubeAxis = ({flipX, flipY, flipDown}: {flipX: boolean, flipY: boolean, flipDown: boolean}) =>{ - const {dimArrays, dimNames, dimUnits, dataShape, revY, is4D} = useGlobalStore(useShallow(state => ({ - dimArrays: state.dimArrays, - dimNames: state.dimNames, - dimUnits: state.dimUnits, + const {axisDimArrays, axisDimNames, axisDimUnits, dataShape, revY, shape} = useGlobalStore(useShallow(state => ({ + axisDimArrays: state.axisDimArrays, + axisDimNames: state.axisDimNames, + axisDimUnits: state.axisDimUnits, dataShape: state.dataShape, revY: state.flipY, - is4D: state.is4D + shape: state.shape }))) - const {xRange, yRange, zRange, plotType, timeScale, animProg, zSlice, ySlice, xSlice, coarsen} = usePlotStore(useShallow(state => ({ + const {xRange, yRange, zRange, plotType, animProg, zSlice, ySlice, xSlice, coarsen, permute} = usePlotStore(useShallow(state => ({ xRange: state.xRange, yRange: state.yRange, - zRange: state.zRange, plotType: state.plotType, - timeScale: state.timeScale, animProg: state.animProg, + zRange: state.zRange, plotType: state.plotType, animProg: state.animProg, zSlice: state.zSlice, ySlice: state.ySlice, - xSlice: state.xSlice, coarsen: state.coarsen, + xSlice: state.xSlice, coarsen: state.coarsen,permute:state.permute }))) const {hideAxis, hideAxisControls} = useImageExportStore(useShallow( state => ({ hideAxis: state.hideAxis, hideAxisControls: state.hideAxisControls }))) - const shapeLength = dimArrays.length - + const shapeLength = axisDimArrays.length const dimSlices = useMemo(()=> { + const slicers = permuteArr([zSlice, ySlice, xSlice], permute) + const [xSlicer, ySlicer, zSlicer] = slicers let slices = [ - dimArrays[shapeLength-3].slice(zSlice[0], zSlice[1] ? zSlice[1] : undefined), - revY ? dimArrays[shapeLength-2].slice(ySlice[0], ySlice[1] ? ySlice[1] : undefined).reverse() : dimArrays[shapeLength-2].slice(ySlice[0], ySlice[1] ? ySlice[1] : undefined), - dimArrays[shapeLength-1].slice(xSlice[0], xSlice[1] ? xSlice[1] : undefined), + axisDimArrays[shapeLength-3].slice(zSlicer[0], zSlicer[1] ? zSlicer[1] : undefined), + revY ? axisDimArrays[shapeLength-2].slice(ySlicer[0], ySlicer[1] ? ySlicer[1] : undefined).reverse() : axisDimArrays[shapeLength-2].slice(ySlicer[0], ySlicer[1] ? ySlicer[1] : undefined), + axisDimArrays[shapeLength-1].slice(xSlicer[0], xSlicer[1] ? xSlicer[1] : undefined), ] if (coarsen) { const {kernelDepth, kernelSize} = useZarrStore.getState() slices = slices.map((val, idx) => coarsenFlatArray(val, (idx === 0 ? kernelDepth : kernelSize))) } return slices - },[revY, dimArrays, zSlice, ySlice, xSlice, coarsen]) + },[revY, axisDimArrays, zSlice, ySlice, xSlice, coarsen]) + const dimLengths = dimSlices.map(val => val.length) const [xResolution, setXResolution] = useState(AXIS_CONSTANTS.INITIAL_RESOLUTION) const [yResolution, setYResolution] = useState(AXIS_CONSTANTS.INITIAL_RESOLUTION) const [zResolution, setZResolution] = useState(AXIS_CONSTANTS.INITIAL_RESOLUTION) + const permuteShape = useMemo(()=>permuteArr(shape.toArray().reverse(),permute),[shape, permute]) const isPC = useMemo(()=>plotType == 'point-cloud',[plotType]) - const globalScale = isPC ? dataShape[2]/AXIS_CONSTANTS.PC_GLOBAL_SCALE_DIVISOR : 1 - - const depthRatio = useMemo(()=>dataShape[0]/dataShape[2]*timeScale,[dataShape, timeScale]); - const shapeRatio = useMemo(()=>dataShape[1]/dataShape[2], [dataShape]) - const timeRatio = Math.max(dataShape[0]/dataShape[2], 2); - + const globalScale = isPC ? permuteShape[2]/AXIS_CONSTANTS.PC_GLOBAL_SCALE_DIVISOR : 1 + const xScale = isPC + ? dataShape[permute?.indexOf(2)??2]*globalScale + : permuteShape[2]/2*globalScale; + const yScale = isPC + ? dataShape[permute?.indexOf(1)??2]*globalScale + :permuteShape[1]/2*globalScale; + const zScale = isPC + ? dataShape[permute?.indexOf(0)??2]*globalScale + :permuteShape[0]/2*globalScale; + + const rangers = useMemo(()=>permuteArr([zRange,yRange,xRange],permute),[yRange,zRange, xRange, permute]) + const xRanger = rangers[2]; + const yRanger = rangers[1]; + const zRanger = rangers[0] + const secondaryColor = useCSSVariable('--text-plot') //replace with needed variable const colorHex = useMemo(()=>{ if (!secondaryColor){return} @@ -92,20 +105,20 @@ const CubeAxis = ({flipX, flipY, flipDown}: {flipX: boolean, flipY: boolean, fli const tickLength = AXIS_CONSTANTS.TICK_LENGTH_FACTOR*globalScale; const xLine = useMemo(()=> { - const geom = new LineSegmentsGeometry().setPositions([xRange[0]*globalScale-tickLength/2, 0, 0, xRange[1]*globalScale+tickLength/2, 0, 0]); - return new LineSegments2(geom, lineMat)},[xRange, lineMat, globalScale]) + const geom = new LineSegmentsGeometry().setPositions([xRanger[0]*xScale-tickLength/2, 0, 0, xRanger[1]*xScale+tickLength/2, 0, 0]); + return new LineSegments2(geom, lineMat)},[xRanger, lineMat, xScale]) const yLine = useMemo(() =>{ - const geom = new LineSegmentsGeometry().setPositions([0, yRange[0]*shapeRatio*globalScale, 0, 0, yRange[1]*shapeRatio*globalScale+tickLength/2, 0]); - return new LineSegments2(geom, lineMat)},[yRange, shapeRatio, lineMat, globalScale]) + const geom = new LineSegmentsGeometry().setPositions([0, yRanger[0]*yScale, 0, 0, yRanger[1]*yScale+tickLength/2, 0]); + return new LineSegments2(geom, lineMat)},[yRanger, yScale, lineMat]) const zLine = useMemo(()=> { - const geom = new LineSegmentsGeometry().setPositions([0, 0, isPC ? zRange[0]*globalScale*depthRatio-tickLength/2 : (zRange[0]*timeRatio-tickLength)/2, 0, 0, isPC ? zRange[1]*globalScale*depthRatio+tickLength/2 : (zRange[1]*timeRatio+tickLength)/2]); - return new LineSegments2(geom, lineMat)},[zRange, depthRatio, isPC, lineMat, globalScale]) + const geom = new LineSegmentsGeometry().setPositions([0, 0, isPC ? zRanger[0]*zScale-tickLength/2 : zRanger[0]*zScale-tickLength/2, 0, 0, isPC ? zRanger[1]*zScale+tickLength/2 : zRanger[1]*zScale+tickLength/2]); + return new LineSegments2(geom, lineMat)},[zRanger, zScale, isPC, lineMat]) const tickLine = useMemo(()=> { const geom = new LineSegmentsGeometry().setPositions([0, 0, 0, 0, 0, tickLength]); - return new LineSegments2(geom, lineMat)},[lineMat, globalScale]) + return new LineSegments2(geom, lineMat)},[lineMat, tickLength]) const xDimScale = xResolution/(xResolution-1) const xValDelta = 1/(xResolution-1) @@ -114,22 +127,24 @@ const CubeAxis = ({flipX, flipY, flipDown}: {flipX: boolean, flipY: boolean, fli const zDimScale = zResolution/(zResolution-1) const zValDelta = 1/(zResolution-1) - const xTitleOffset = useMemo(() => (dimNames[shapeLength - 1]?.length * AXIS_CONSTANTS.TITLE_FONT_SIZE_FACTOR / 2 + 0.1) * globalScale, [dimNames, globalScale]); - const yTitleOffset = useMemo(() => (dimNames[shapeLength - 2]?.length * AXIS_CONSTANTS.TITLE_FONT_SIZE_FACTOR / 2 + 0.1) * globalScale, [dimNames, globalScale]); - const zTitleOffset = useMemo(() => (dimNames[shapeLength - 3]?.length * AXIS_CONSTANTS.TITLE_FONT_SIZE_FACTOR / 2 + 0.1) * globalScale, [dimNames, globalScale]); - + const xTitleOffset = useMemo(() => (axisDimNames[shapeLength - 1].length * AXIS_CONSTANTS.TITLE_FONT_SIZE_FACTOR / 2 + 0.1) * globalScale, [axisDimNames, globalScale]); + const yTitleOffset = useMemo(() => (axisDimNames[shapeLength - 2].length * AXIS_CONSTANTS.TITLE_FONT_SIZE_FACTOR / 2 + 0.1) * globalScale, [axisDimNames, globalScale]); + const zTitleOffset = useMemo(() => (axisDimNames[shapeLength - 3].length * AXIS_CONSTANTS.TITLE_FONT_SIZE_FACTOR / 2 + 0.1) * globalScale, [axisDimNames, globalScale]); + return ( {/* Horizontal Group */} - + {/* X Group */} - + {Array(xResolution).fill(null).map((_val,idx)=>( - (((xRange[0] + 1)/2) <= (idx*xDimScale)/xResolution && - ((xRange[1] + 1)/2) >= (idx*xDimScale)/xResolution) + (((xRanger[0] + 1)/2) <= (idx*xDimScale)/xResolution && + ((xRanger[1] + 1)/2) >= (idx*xDimScale)/xResolution) && - + {parseLoc(dimSlices[2][Math.floor((dimLengths[2]-1)*idx*xValDelta)],dimUnits[shapeLength - 1])} + >{parseLoc(dimSlices[2][Math.floor((dimLengths[2]-1)*idx*xValDelta)],axisDimUnits[shapeLength - 1])} ))} - + {dimNames[shapeLength - 1]} + >{axisDimNames[shapeLength - 1]} {xResolution < AXIS_CONSTANTS.MAX_RESOLUTION && {/* Z Group */} - + {Array(zResolution).fill(null).map((_val,idx)=>( - (((zRange[0] + 1)/2) <= (idx*zDimScale)/zResolution && - ((zRange[1] + 1)/2) >= (idx*zDimScale)/zResolution ) + (((zRanger[0] + 1)/2) <= (idx*zDimScale)/zResolution && + ((zRanger[1] + 1)/2) >= (idx*zDimScale)/zResolution ) && - + {parseLoc(dimSlices[0][(Math.floor((dimLengths[0]-1)*idx*zValDelta)+Math.floor(dimLengths[0]*animProg))%dimLengths[0]],dimUnits[shapeLength - 3])} + >{parseLoc(dimSlices[0][(Math.floor((dimLengths[0]-1)*idx*zValDelta)+Math.floor(dimLengths[0]*animProg))%dimLengths[0]],axisDimUnits[shapeLength - 3])} ))} - + {dimNames[shapeLength - 3]} + >{axisDimNames[shapeLength - 3]} {zResolution < AXIS_CONSTANTS.MAX_RESOLUTION && @@ -253,13 +270,13 @@ const CubeAxis = ({flipX, flipY, flipDown}: {flipX: boolean, flipY: boolean, fli {/* Vertical Group */} - + {Array(yResolution).fill(null).map((_val,idx)=>( - (((yRange[0] + 1)/2) <= (idx*yDimScale)/yResolution && - ((yRange[1] + 1)/2) >= (idx*yDimScale)/yResolution) + (((yRanger[0] + 1)/2) <= (idx*yDimScale)/yResolution && + ((yRanger[1] + 1)/2) >= (idx*yDimScale)/yResolution) && - + {parseLoc(dimSlices[1][Math.floor((dimLengths[1]-1)*idx*yValDelta)],dimUnits[shapeLength - 2])} + >{parseLoc(dimSlices[1][Math.floor((dimLengths[1]-1)*idx*yValDelta)],axisDimUnits[shapeLength - 2])} ))} - + - {dimNames[shapeLength - 2]} + {axisDimNames[shapeLength - 2]} @@ -342,17 +359,17 @@ const FLAT_AXIS_CONSTANTS = { }; const FlatAxis = () =>{ - const {dimArrays, dimNames, dimUnits, flipY} = useGlobalStore(useShallow(state => ({ - dimArrays: state.dimArrays, - dimNames: state.dimNames, - dimUnits: state.dimUnits, + const {axisDimArrays, axisDimNames, axisDimUnits, flipY} = useGlobalStore(useShallow(state => ({ + axisDimArrays: state.axisDimArrays, + axisDimNames: state.axisDimNames, + axisDimUnits: state.axisDimUnits, flipY: state.flipY, }))) - const {plotType, zSlice, ySlice, xSlice, rotateFlat} = usePlotStore(useShallow(state=>({ + const {plotType, zSlice, ySlice, xSlice, permute} = usePlotStore(useShallow(state=>({ plotType: state.plotType, zSlice: state.zSlice, ySlice: state.ySlice, xSlice: state.xSlice, - rotateFlat:state.rotateFlat + permute:state.permute }))) const {hideAxis, hideAxisControls} = useImageExportStore(useShallow( state => ({ @@ -364,22 +381,29 @@ const FlatAxis = () =>{ analysisMode: state.analysisMode, axis: state.axis }))) - const shapeLength = dimArrays.length; - const is4D = dimArrays.length === 4; - const originallyFlat = dimArrays.length == 2; + const {rotateX, rotateZ, mirrorHorizontal, mirrorVertical} = usePlotTransformStore(useShallow(state=> ({ + rotateX: state.rotateX, + rotateZ: state.rotateZ, + mirrorHorizontal: state.mirrorHorizontal, + mirrorVertical: state.mirrorVertical + }))) + + const shapeLength = axisDimArrays.length; + const is4D = shapeLength === 4; + const originallyFlat = shapeLength == 2; const slices = originallyFlat ? [ySlice, xSlice] : [zSlice, ySlice, xSlice] const dimSlices = useMemo(()=>originallyFlat ? [ - flipY ? dimArrays[0].slice().reverse() : dimArrays[0], // Need the slice because inside useMemo it doesn't mutate the original properly - dimArrays[1] + flipY ? axisDimArrays[0].slice().reverse() : axisDimArrays[0], // Need the slice because inside useMemo it doesn't mutate the original properly + axisDimArrays[1] ] : [ - dimArrays[shapeLength-3].slice(zSlice[0], zSlice[1] ? zSlice[1] : undefined), - flipY ? dimArrays[shapeLength-2].slice(ySlice[0], ySlice[1] ? ySlice[1] : undefined).reverse() : dimArrays[shapeLength-2].slice(ySlice[0], ySlice[1] ? ySlice[1] : undefined), - dimArrays[shapeLength-1].slice(xSlice[0], xSlice[1] ? xSlice[1] : undefined), - ],[dimArrays, flipY]) + axisDimArrays[shapeLength-3].slice(zSlice[0], zSlice[1] ? zSlice[1] : undefined), + flipY ? axisDimArrays[shapeLength-2].slice(ySlice[0], ySlice[1] ? ySlice[1] : undefined).reverse() : axisDimArrays[shapeLength-2].slice(ySlice[0], ySlice[1] ? ySlice[1] : undefined), + axisDimArrays[shapeLength-1].slice(xSlice[0], xSlice[1] ? xSlice[1] : undefined), + ],[axisDimArrays, flipY]) const dimLengths = useMemo(()=>{ if (analysisMode && !originallyFlat){ @@ -391,8 +415,10 @@ const FlatAxis = () =>{ },[axis, dimSlices, analysisMode]) const swap = useMemo(() => (analysisMode && axis == 2 && !originallyFlat),[axis, analysisMode]) // This is for the horrible case when users plot along the horizontal dimension i.e; Longitude. Everything swaps - const widthIdx = swap ? dimLengths.length-2 : dimLengths.length-1 - const heightIdx = swap ? dimLengths.length-1 : dimLengths.length-2 + let widthIdx = swap ? dimLengths.length-2 : dimLengths.length-1 + let heightIdx = swap ? dimLengths.length-1 : dimLengths.length-2 + widthIdx = permute?.indexOf(widthIdx)?? widthIdx + heightIdx = permute?.indexOf(heightIdx)?? heightIdx const [xResolution, setXResolution] = useState(FLAT_AXIS_CONSTANTS.INITIAL_RESOLUTION) const [yResolution, setYResolution] = useState(FLAT_AXIS_CONSTANTS.INITIAL_RESOLUTION) @@ -403,30 +429,30 @@ const FlatAxis = () =>{ return { axisArrays: dimSlices.filter((_val, idx) => idx != axis), axisUnits: is4D - ? dimUnits.slice(1).filter((_val, idx) => idx != axis) - : dimUnits.filter((_val, idx) => idx != axis), + ? axisDimUnits.slice(1).filter((_val, idx) => idx != axis) + : axisDimUnits.filter((_val, idx) => idx != axis), axisNames: is4D - ? dimNames.slice(1).filter((_val, idx) => idx != axis) - : dimNames.filter((_val, idx) => idx != axis) + ? axisDimNames.slice(1).filter((_val, idx) => idx != axis) + : axisDimNames.filter((_val, idx) => idx != axis) }; } else if (!originallyFlat) { return { axisArrays: dimSlices, axisUnits: is4D - ? dimUnits.slice(1) - : dimUnits, + ? axisDimUnits.slice(1) + : axisDimUnits, axisNames: is4D - ? dimNames.slice(1) - : dimNames + ? axisDimNames.slice(1) + : axisDimNames }; } else { return { axisArrays: dimSlices, - axisUnits: dimUnits, - axisNames: dimNames, + axisUnits: axisDimUnits, + axisNames: axisDimNames, }; } - }, [analysisMode, dimArrays, dimUnits, is4D, dimNames, dimSlices]); + }, [analysisMode, axisDimArrays, axisDimUnits, is4D, axisDimNames, dimSlices]); const shapeRatio = useMemo(()=>{ @@ -464,14 +490,17 @@ const FlatAxis = () =>{ const xTitleOffset = useMemo(() => (axisNames[widthIdx].length * FLAT_AXIS_CONSTANTS.TITLE_FONT_SIZE / 2 + 0.1), [axisNames, widthIdx]); const yTitleOffset = useMemo(() => (axisNames[heightIdx].length * FLAT_AXIS_CONSTANTS.TITLE_FONT_SIZE / 2 + 0.1), [axisNames, heightIdx]); - + return ( {/* X Group */} - + {Array(xResolution).fill(null).map((_val,idx)=>( xResolution > FLAT_AXIS_CONSTANTS.MIN_RESOLUTION && diff --git a/src/components/plots/CountryBorders.tsx b/src/components/plots/CountryBorders.tsx index 94d45696..42cec28e 100644 --- a/src/components/plots/CountryBorders.tsx +++ b/src/components/plots/CountryBorders.tsx @@ -8,6 +8,7 @@ import { useShallow } from 'zustand/shallow'; import { useFrame } from '@react-three/fiber'; import {vertexShader, bordersFrag} from '../textures/shaders' import { invalidate } from '@react-three/fiber'; +import { usePlotTransformStore } from '@/GlobalStates/PlotTransformStore'; function Reproject([lon, lat] : [number, number], lonBounds: [number, number], latBounds: [number, number]){ let newLon = (lon-lonBounds[0])/(lonBounds[1]-lonBounds[0]); @@ -41,7 +42,6 @@ function Borders({features}:{features: any}){ flipY: state.flipY, shape: state.shape }))) - const [lonBounds, latBounds] = useMemo(()=>{ //The bounds for the shader. It takes the middle point of the furthest coordinate and adds the distance to edge of pixel const newLatStep = latResolution/2; const newLonStep = lonResolution/2; @@ -212,7 +212,12 @@ const CountryBorders = () => { analysisMode: state.analysisMode, axis: state.axis }))) - + const {rotateX, rotateZ, mirrorHorizontal, mirrorVertical} = usePlotTransformStore(useShallow(state=> ({ + rotateX: state.rotateX, + rotateZ: state.rotateZ, + mirrorHorizontal: state.mirrorHorizontal, + mirrorVertical: state.mirrorVertical + }))) const [spherize, setSpherize] = useState(false) useEffect(()=>{ @@ -252,11 +257,12 @@ const CountryBorders = () => { const aspectRatio = dataShape[2]/dataShape[1] const globalScale = isPC ? dataShape[2]/500 : 1 - + let yScale = mirrorVertical ? -1 : 1; + // yScale *= flipY ? -1 : 1; return( { vTransferScale: state.vTransferScale, fillValue: state.fillValue, }))) + const {rotateX, rotateZ, mirrorHorizontal, mirrorVertical} = usePlotTransformStore(useShallow(state=> ({ + rotateX: state.rotateX, + rotateZ: state.rotateZ, + mirrorHorizontal: state.mirrorHorizontal, + mirrorVertical: state.mirrorVertical + }))) const meshRef = useRef(null!); const aspectRatio = shape.y/shape.x const timeRatio = shape.z/shape.x; @@ -73,7 +80,6 @@ export const DataCube = ({ volTexture }: DataCubeProps ) => { fragmentShader: useFragOpt ? fragOpt : fragmentShader, transparent: true, blending: THREE.NormalBlending, - depthWrite: false, side: useOrtho ? THREE.FrontSide : THREE.BackSide, }),[useFragOpt, useOrtho, volTexture]); @@ -109,10 +115,21 @@ export const DataCube = ({ volTexture }: DataCubeProps ) => { .copy(meshRef.current.modelViewMatrix) .invert(); }) + const flipState = flipY ? -1 : 1 return ( - - - + + ) diff --git a/src/components/plots/FlatBlocks.tsx b/src/components/plots/FlatBlocks.tsx index 2bddce35..e21b5068 100644 --- a/src/components/plots/FlatBlocks.tsx +++ b/src/components/plots/FlatBlocks.tsx @@ -10,6 +10,7 @@ import { invalidate } from '@react-three/fiber' import { deg2rad } from '@/utils/HelperFuncs' import { useCoordBounds } from '@/hooks/useCoordBounds' import { GetVert } from '../textures/GetVert'; +import { usePlotTransformStore } from '@/GlobalStates/PlotTransformStore'; const FlatBlocks = ({textures} : {textures: THREE.Data3DTexture[] | THREE.DataTexture[] | null}) => { const {colormap, isFlat, valueScales, flipY, @@ -32,6 +33,12 @@ const FlatBlocks = ({textures} : {textures: THREE.Data3DTexture[] | THREE.DataTe const {analysisMode, axis} = useAnalysisStore(useShallow(state => ({ analysisMode: state.analysisMode, axis:state.axis }))) + const {rotateX, rotateZ, mirrorHorizontal, mirrorVertical} = usePlotTransformStore(useShallow(state=> ({ + rotateX: state.rotateX, + rotateZ: state.rotateZ, + mirrorHorizontal: state.mirrorHorizontal, + mirrorVertical: state.mirrorVertical + }))) const {width, height} = useMemo(()=>{ if (dataShape.length == 2){ return {width: dataShape[1], height: dataShape[0]} @@ -130,13 +137,18 @@ const FlatBlocks = ({textures} : {textures: THREE.Data3DTexture[] | THREE.DataTe },[animProg, valueScales, displacement, colormap, cScale, cOffset, offsetNegatives, valueRange, textures, fillValue, analysisMode, axis, width, height, latBounds, lonBounds, maskValue]) return ( - - + + + + ) } diff --git a/src/components/plots/FlatMap.tsx b/src/components/plots/FlatMap.tsx index 9d86eca7..b932abeb 100644 --- a/src/components/plots/FlatMap.tsx +++ b/src/components/plots/FlatMap.tsx @@ -14,6 +14,7 @@ import { evaluate_cmap } from 'js-colormaps-es'; import { useCoordBounds } from '@/hooks/useCoordBounds'; import { GetFrag } from '../textures'; import { SquareMeshes } from './TransectMeshes'; +import { usePlotTransformStore } from '@/GlobalStates/PlotTransformStore'; interface InfoSettersProps{ setLoc: React.Dispatch>; setShowInfo: React.Dispatch>; @@ -58,7 +59,12 @@ const FlatMap = ({textures, infoSetters} : {textures : THREE.DataTexture[] | THR kernelSize: state.kernelSize, kernelDepth: state.kernelDepth }))) - + const {rotateX, rotateZ, mirrorHorizontal, mirrorVertical} = usePlotTransformStore(useShallow(state=> ({ + rotateX: state.rotateX, + rotateZ: state.rotateZ, + mirrorHorizontal: state.mirrorHorizontal, + mirrorVertical: state.mirrorVertical + }))) const shapeLength = dimArrays.length const dimSlices = useMemo (() => { @@ -184,6 +190,9 @@ const FlatMap = ({textures, infoSetters} : {textures : THREE.DataTexture[] | THR vertexShader: vertShader, fragmentShader: GetFrag("flatFrag", isFlat), side: THREE.DoubleSide, + depthWrite:false, + depthTest: true, + transparent:false }),[isFlat, textures]) useEffect(()=>{ @@ -204,19 +213,22 @@ const FlatMap = ({textures, infoSetters} : {textures : THREE.DataTexture[] | THR },[cScale, cOffset, colormap, animProg, nanColor, nanTransparency, latBounds, lonBounds, fillValue, maskValue, valueRange]) return ( - <> - - {setShowInfo(true); infoRef.current = true }} - onPointerLeave={()=>{setShowInfo(false); infoRef.current = false }} - onPointerMove={handleMove} - onClick={selectTS && HandleTimeSeries} - /> - + + + {setShowInfo(true); infoRef.current = true }} + onPointerLeave={()=>{setShowInfo(false); infoRef.current = false }} + onPointerMove={handleMove} + onClick={selectTS && HandleTimeSeries} + /> + ) } diff --git a/src/components/plots/Plot.tsx b/src/components/plots/Plot.tsx index 344d8ef9..9812c04a 100644 --- a/src/components/plots/Plot.tsx +++ b/src/components/plots/Plot.tsx @@ -1,13 +1,16 @@ import { OrbitControls, useTexture } from '@react-three/drei'; import React, { useMemo, useRef, useState, useEffect } from 'react'; import * as THREE from 'three'; -import { PointCloud, DataCube, FlatMap, Sphere, CountryBorders, AxisLines, SphereBlocks, FlatBlocks, KeyFramePreviewer } from '@/components/plots'; +import { PointCloud, DataCube, FlatMap, Sphere, + CountryBorders, AxisLines, SphereBlocks, FlatBlocks, + KeyFramePreviewer, TransformAxis } from '@/components/plots'; import { Canvas, invalidate, useThree } from '@react-three/fiber'; import { CreateTexture } from '@/components/textures'; import { useAnalysisStore } from '@/GlobalStates/AnalysisStore'; import { useGlobalStore } from '@/GlobalStates/GlobalStore'; import { usePlotStore } from '@/GlobalStates/PlotStore'; import { useImageExportStore } from '@/GlobalStates/ImageExportStore'; +import { usePlotTransformStore } from '@/GlobalStates/PlotTransformStore'; import { useShallow } from 'zustand/shallow'; import { Navbar, Colorbar, ExportExtent, ShaderEditor, KeyFrames } from '../ui'; import AnalysisInfo from './AnalysisInfo'; @@ -39,7 +42,6 @@ const Orbiter = ({isFlat} : {isFlat : boolean}) =>{ const hasMounted = useRef(false); const cameraRef = useRef(null) const {set, camera, size} = useThree() - // Reset Camera Position and Target useEffect(()=>{ if (!hasMounted.current) { @@ -82,7 +84,6 @@ const Orbiter = ({isFlat} : {isFlat : boolean}) =>{ } },[resetCamera, isFlat]) - // ---- Switch from Perspective to Orthographic ---- // useEffect(()=>{ if (hasMounted.current){ let newCamera; @@ -119,7 +120,6 @@ const Orbiter = ({isFlat} : {isFlat : boolean}) =>{ } },[useOrtho]) - // ---- Move camera to position ---- // useEffect(()=>{ const cam = cameraRef.current const controls = orbitRef.current @@ -139,14 +139,6 @@ const Orbiter = ({isFlat} : {isFlat : boolean}) =>{ } },[cameraPosition]) - // ---- Camera Ref for state saves ---- // - useEffect(()=>{ - usePlotStore.setState({camera}) - return () => { - usePlotStore.setState({camera: undefined}) - } - },[camera]) - return ( { } const Plot = () => { - const {colormap, isFlat, DPR, valueScales, setIsFlat} = useGlobalStore(useShallow(state=>({ + + const {colormap, isFlat, DPR, valueScales, dimNames, dimUnits, dimArrays, dataShape, setIsFlat} = useGlobalStore(useShallow(state=>({ colormap: state.colormap, isFlat: state.isFlat, DPR: state.DPR, valueScales: state.valueScales, + dimNames: state.dimNames, dimUnits: state.dimUnits, dimArrays: state.dimArrays, dataShape: state.dataShape, setIsFlat: state.setIsFlat, }))) + const {setAxisDimArrays, setAxisDimNames, setAxisDimUnits, setAxisOrder, setAxisShape, setAxisFlipped} = useGlobalStore(useShallow(state => ({ + setAxisDimArrays: state.setAxisDimArrays, + setAxisDimNames: state.setAxisDimNames, + setAxisDimUnits: state.setAxisDimUnits, + setAxisOrder: state.setAxisOrder, + setAxisShape: state.setAxisShape, + setAxisFlipped: state.setAxisFlipped + }))) const {keyFrameEditor} = useImageExportStore(useShallow(state => ({ keyFrameEditor:state.keyFrameEditor}))) - const {plotType, displaceSurface} = usePlotStore(useShallow(state => ({ + const {plotType, displaceSurface, zSlice, ySlice, xSlice,} = usePlotStore(useShallow(state => ({ plotType: state.plotType, displaceSurface: state.displaceSurface, + zSlice: state.zSlice, ySlice: state.ySlice, xSlice: state.xSlice, }))) const {analysisMode, useEditor} = useAnalysisStore(useShallow(state => ({ analysisMode: state.analysisMode, @@ -181,6 +184,78 @@ const Plot = () => { const [showInfo, setShowInfo] = useState(false) const [loc, setLoc] = useState([0,0]) + + const {rotateX, rotateZ, mirrorHorizontal, mirrorVertical} = usePlotTransformStore(useShallow(state=> ({ + rotateX: state.rotateX, + rotateZ: state.rotateZ, + mirrorHorizontal: state.mirrorHorizontal, + mirrorVertical: state.mirrorVertical + }))) + + // ---- Transformation Handlers ---- // + useEffect(()=>{ + let axisMapping = [0, 1, 2]; + let axisReversed = [false, false, false]; + const origSlices = [zSlice, ySlice, xSlice] + + if (rotateZ === 1){ + // 90: X=-Y, Y=X + axisMapping = [axisMapping[0], axisMapping[2], axisMapping[1]]; + axisReversed = [axisReversed[0], axisReversed[2], !axisReversed[1]]; + } else if ( rotateZ === 2){ + // 180: X=-X, Y=-Y + axisReversed = [axisReversed[0], !axisReversed[1], !axisReversed[2]]; + } else if (rotateZ === 3) { + // 270: X=Y, Y=-X + axisMapping = [axisMapping[0], axisMapping[2], axisMapping[1]]; + axisReversed = [axisReversed[0], !axisReversed[2], axisReversed[1]]; + } + + if (rotateX === 1){ + // 90: Y=-Z, Z=Y + axisMapping = [axisMapping[1], axisMapping[0], axisMapping[2]]; + axisReversed = [axisReversed[1], !axisReversed[0], axisReversed[2]]; + } + else if (rotateX === 2){ + // 180: Y=-Y, Z=-Z + axisReversed = [!axisReversed[1], !axisReversed[0], axisReversed[2]]; + } else if (rotateX === 3){ + // 270: Y=Z, Z=-Y + axisMapping = [axisMapping[1], axisMapping[0], axisMapping[2]]; + axisReversed = [!axisReversed[1], axisReversed[0], axisReversed[2]]; + } + + if (mirrorHorizontal) { + axisReversed[2] = !axisReversed[2]; + } + if (mirrorVertical) { + axisReversed[1] = !axisReversed[1]; + } + + const transformedDimArrays = axisMapping.map((origIdx, newIdx) =>{ + const arr = dimArrays[origIdx].slice(); + if (axisReversed[newIdx]) arr.reverse() + return arr + }) + + const transformedDimNames = axisMapping.map(origIdx => dimNames[origIdx]) + const transformedDimUnits = axisMapping.map(origIdx => dimUnits[origIdx]) + const axisShape = axisMapping.map(origIdx => dataShape[origIdx]/dataShape[dataShape.length-1]) + const axisSlices = axisMapping.map(origIdx => origSlices[origIdx]) + setAxisDimArrays(transformedDimArrays) + setAxisDimNames(transformedDimNames) + setAxisDimUnits(transformedDimUnits) + setAxisShape(axisShape) + setAxisOrder(axisMapping) + setAxisFlipped(axisReversed) + usePlotStore.setState({ + zSlice:axisSlices[0], + ySlice:axisSlices[1], + xSlice:axisSlices[2], + permute:axisMapping + }) + + },[rotateX, rotateZ, mirrorHorizontal, mirrorVertical, dimArrays, dimNames, dimUnits]) //DATA LOADING const {textures, show, stableMetadata, setTextures} = useDataFetcher() @@ -235,6 +310,7 @@ const Plot = () => { gl={{ preserveDrawingBuffer: true }} dpr={[DPR,DPR]} > + diff --git a/src/components/plots/PointCloud.tsx b/src/components/plots/PointCloud.tsx index dafeffa4..4828b83c 100644 --- a/src/components/plots/PointCloud.tsx +++ b/src/components/plots/PointCloud.tsx @@ -8,6 +8,7 @@ import { deg2rad } from '@/utils/HelperFuncs'; import { useCoordBounds } from '@/hooks/useCoordBounds'; import { UVCube } from './UVCube'; import { ColumnMeshes } from './TransectMeshes'; +import { usePlotTransformStore } from '@/GlobalStates/PlotTransformStore'; interface PCProps { texture: THREE.Data3DTexture[] | null, @@ -63,7 +64,12 @@ export const PointCloud = ({textures} : {textures:PCProps} )=>{ maskValue: state.maskValue, disablePointScale: state.disablePointScale }))) - + const {rotateX, rotateZ, mirrorHorizontal, mirrorVertical} = usePlotTransformStore(useShallow(state=> ({ + rotateX: state.rotateX, + rotateZ: state.rotateZ, + mirrorHorizontal: state.mirrorHorizontal, + mirrorVertical: state.mirrorVertical + }))) //Extract data and shape from Data3DTexture const { data, width, height, depth } = useMemo(() => { const [depth, height, width] = dataShape @@ -139,8 +145,13 @@ export const PointCloud = ({textures} : {textures:PCProps} )=>{ } }, [pointSize, colormap, cOffset, cScale, valueRange, scalePoints, scaleIntensity, animProg, timeScale, xRange, yRange, fillValue, zRange, maskValue, lonBounds, latBounds]); const tsScale = dataShape[2]/500 + let yScale = mirrorVertical ? -1 : 1; + yScale *= flipY ? -1 : 1; return ( - + diff --git a/src/components/plots/Sphere.tsx b/src/components/plots/Sphere.tsx index a9eb589e..58c6f574 100644 --- a/src/components/plots/Sphere.tsx +++ b/src/components/plots/Sphere.tsx @@ -3,6 +3,7 @@ import * as THREE from 'three' import { useAnalysisStore } from '@/GlobalStates/AnalysisStore'; import { useGlobalStore } from '@/GlobalStates/GlobalStore'; import { usePlotStore } from '@/GlobalStates/PlotStore'; +import { usePlotTransformStore } from '@/GlobalStates/PlotTransformStore'; import { useShallow } from 'zustand/shallow' import { parseUVCoords, GetTimeSeries, GetCurrentArray, deg2rad } from '@/utils/HelperFuncs'; import { evaluate_cmap } from 'js-colormaps-es'; @@ -40,7 +41,12 @@ export const Sphere = ({textures} : {textures: THREE.Data3DTexture[] | THREE.Dat flipY: state.flipY, textureArrayDepths: state.textureArrayDepths }))) - + const {rotateX, rotateZ, mirrorHorizontal, mirrorVertical} = usePlotTransformStore(useShallow(state=> ({ + rotateX: state.rotateX, + rotateZ: state.rotateZ, + mirrorHorizontal: state.mirrorHorizontal, + mirrorVertical: state.mirrorVertical + }))) const {animate, animProg, cOffset, cScale, valueRange, selectTS, nanColor, nanTransparency, sphereDisplacement, sphereResolution, zSlice, ySlice, xSlice, fillValue, borderTexture, maskTexture, maskValue, getColorIdx, incrementColorIdx} = usePlotStore(useShallow(state=> ({ @@ -178,12 +184,15 @@ export const Sphere = ({textures} : {textures: THREE.Data3DTexture[] | THREE.Dat } return ( - <> - - + + + + + selectTS && HandleTimeSeries(e)}/> + - selectTS && HandleTimeSeries(e)}/> - - ) } diff --git a/src/components/plots/SphereBlocks.tsx b/src/components/plots/SphereBlocks.tsx index d32641ff..1bd8fae6 100644 --- a/src/components/plots/SphereBlocks.tsx +++ b/src/components/plots/SphereBlocks.tsx @@ -9,6 +9,7 @@ import { invalidate } from '@react-three/fiber' import { deg2rad } from '@/utils/HelperFuncs' import { useCoordBounds } from '@/hooks/useCoordBounds' import { GetVert } from '../textures'; +import { usePlotTransformStore } from '@/GlobalStates/PlotTransformStore'; const SphereBlocks = ({textures} : {textures: THREE.Data3DTexture[] | THREE.DataTexture[] | null}) => { const {colormap, isFlat, valueScales, dataShape, textureArrayDepths} = useGlobalStore(useShallow(state=>({ @@ -33,6 +34,12 @@ const SphereBlocks = ({textures} : {textures: THREE.Data3DTexture[] | THREE.Data maskTexture: state.maskTexture, maskValue: state.maskValue, }))) + const {rotateX, rotateZ, mirrorHorizontal, mirrorVertical} = usePlotTransformStore(useShallow(state=> ({ + rotateX: state.rotateX, + rotateZ: state.rotateZ, + mirrorHorizontal: state.mirrorHorizontal, + mirrorVertical: state.mirrorVertical + }))) const count = useMemo(()=>{ const width = dataShape[dataShape.length-1]; @@ -136,9 +143,12 @@ const SphereBlocks = ({textures} : {textures: THREE.Data3DTexture[] | THREE.Data invalidate(); } },[nanColor, nanTransparency]) - + let yScale = mirrorVertical ? -1 : 1; return ( - + { + const {showTransformAxis} = usePlotStore(useShallow(state => ({ + showTransformAxis: state.showTransformAxis + }))) + const geom = useMemo(()=>new THREE.CylinderGeometry(.02, .02, 3),[]) + const material = useMemo(() => new THREE.MeshBasicMaterial({color:'red'}) , []) + + return ( + + ) +} diff --git a/src/components/plots/index.ts b/src/components/plots/index.ts index c8c93a6f..754bbf24 100644 --- a/src/components/plots/index.ts +++ b/src/components/plots/index.ts @@ -13,4 +13,5 @@ export {AxisLines} from './AxisLines' export {LandingShapes} from './MorphingPoints' export {SphereBlocks} from './SphereBlocks' export {FlatBlocks} from './FlatBlocks' -export {KeyFramePreviewer} from './KeyFramePreviewer' \ No newline at end of file +export {KeyFramePreviewer} from './KeyFramePreviewer' +export {TransformAxis} from './TransformAxis' \ No newline at end of file diff --git a/src/components/ui/Elements/Icons.tsx b/src/components/ui/Elements/Icons.tsx index 18332c04..a7e78614 100644 --- a/src/components/ui/Elements/Icons.tsx +++ b/src/components/ui/Elements/Icons.tsx @@ -66,5 +66,35 @@ const Potato = ({ color = "currentColor", size = 24, ...props }: IconProps) => ( ); +const Mirror = ({ color = "currentColor", size = 24, ...props }: IconProps) => ( + + + + + +); -export { Perspective, Orthographic, Potato }; \ No newline at end of file +export { Perspective, Orthographic, Potato, Mirror }; \ No newline at end of file diff --git a/src/components/ui/MainPanel/AdjustPlot.tsx b/src/components/ui/MainPanel/AdjustPlot.tsx index 4233513e..fa4ea0a2 100644 --- a/src/components/ui/MainPanel/AdjustPlot.tsx +++ b/src/components/ui/MainPanel/AdjustPlot.tsx @@ -3,6 +3,7 @@ import React, {useState, useEffect} from 'react' import { useAnalysisStore } from '@/GlobalStates/AnalysisStore'; import { useGlobalStore } from '@/GlobalStates/GlobalStore'; import { usePlotStore } from '@/GlobalStates/PlotStore'; +import { usePlotTransformStore } from '@/GlobalStates/PlotTransformStore'; import '../css/MainPanel.css' import { useShallow } from 'zustand/shallow'; import { SliderThumbs } from '@/components/ui/Widgets/SliderThumbs'; @@ -19,7 +20,9 @@ import { parseLoc, normalize, denormalize } from '@/utils/HelperFuncs'; import { BsFillQuestionCircleFill } from "react-icons/bs"; import { ChevronDown } from 'lucide-react'; import {Select, SelectTrigger, SelectContent, SelectItem, SelectValue} from '@/components/ui' -import { RiCloseLargeLine } from "react-icons/ri"; +import { FaArrowRotateRight } from "react-icons/fa6"; +import { Mirror } from '../Elements/Icons';import { RiCloseLargeLine } from "react-icons/ri"; +import { IoMdSwap } from "react-icons/io"; function DeNorm(val : number, min : number, max : number){ const range = max-min; @@ -501,13 +504,13 @@ const SpatialExtent = () =>{ } const GlobalOptions = () =>{ - const {valueRange, showBorders, borderColor, nanColor, nanTransparency, plotType, interpPixels, fillValue, useBorderTexture, + const {valueRange, showBorders, borderColor, nanColor, nanTransparency, plotType, interpPixels, fillValue, useBorderTexture, showTransformAxis, setValueRange, setShowBorders, setBorderColor, setNanColor, setNanTransparency, setInterpPixels, setFillValue} = usePlotStore(useShallow(state => ({ showBorders: state.showBorders, borderColor: state.borderColor, nanColor: state.nanColor, nanTransparency: state.nanTransparency, plotType: state.plotType, interpPixels: state.interpPixels, fillValue: state.fillValue, useBorderTexture:state.useBorderTexture, - valueRange: state.valueRange, setValueRange: state.setValueRange, + valueRange: state.valueRange, showTransformAxis:state.showTransformAxis, setValueRange: state.setValueRange, setShowBorders: state.setShowBorders, setBorderColor: state.setBorderColor, setNanColor: state.setNanColor, setNanTransparency: state.setNanTransparency, setInterpPixels: state.setInterpPixels, setFillValue:state.setFillValue @@ -519,12 +522,166 @@ const GlobalOptions = () =>{ const {valueScales} = useGlobalStore(useShallow(state =>({ valueScales:state.valueScales }))) + const {rotateX, rotateZ, mirrorVertical, mirrorHorizontal, setRotateX, setRotateZ, setMirrorVertical, setMirrorHorizontal} = usePlotTransformStore(useShallow(state => ({ + rotateX: state.rotateX, rotateZ: state.rotateZ, + mirrorVertical: state.mirrorVertical, mirrorHorizontal: state.mirrorHorizontal, + setRotateX: state.setRotateX, setRotateZ: state.setRotateZ, + setMirrorVertical: state.setMirrorVertical, setMirrorHorizontal: state.setMirrorHorizontal + }))) const [thisFillVal, setThisFillValue] = useState(denormalize(fillValue, valueScales.minVal, valueScales.maxVal)) - const [showMasks, setShowMasks] = useState(false) + const [showMasks, setShowMasks] = useState(false); + const [showTransform, setShowTransform] = useState(false); + const [xAngle, setXAngle] = useState(rotateX) + const [zAngle, setZAngle] = useState(rotateZ) + const [rotDir, setRotDir] = useState(1) + const masks = ["None", "Land", "Water"] const isPC = plotType == 'point-cloud' + const canReset = ( + mirrorHorizontal == true || + mirrorVertical == true || + rotateX % 4 != 0 || + rotateZ % 4 != 0 + ); + function ResetTransforms(){ + setRotateX(0); + setRotateZ(0); + setXAngle(0); + setZAngle(0); + setRotDir(1); + setMirrorHorizontal(false); + setMirrorVertical(false) + return null; + } + const mod = (a: number, b: number) => ((a % b) + b) % b; // JS doesn't actually have a module operator. So need our own return (
+ + +
+ {/* Buttons */} +
+ + {canReset && } +
+ {/* Rotations */} +
+ Rotation +
+
+ + + +
+ + {/* Mirror */} +
+ Mirror +
+ + +
+
Value Cropping diff --git a/src/components/ui/MainPanel/CuratedDatasets.tsx b/src/components/ui/MainPanel/CuratedDatasets.tsx new file mode 100644 index 00000000..3db1c35f --- /dev/null +++ b/src/components/ui/MainPanel/CuratedDatasets.tsx @@ -0,0 +1,102 @@ +"use client"; + +import React, { useState } from 'react'; +import { DATASETS_COLLECTION } from './DatasetCollection'; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command"; +import { ChevronDown } from 'lucide-react'; + +const PAGE_SIZE = 5; + +type Props = { + activeOption: string; + setActiveOption: (key: string) => void; + setInitStore: (v: string) => void; + onOpenDescription: () => void; +}; + +const CuratedDatasets = ({ + activeOption, + setActiveOption, + setInitStore, + onOpenDescription, +}: Props) => { + const [search, setSearch] = useState(''); + const [showAll, setShowAll] = useState(false); + + const filtered = DATASETS_COLLECTION.filter(ds => + ds.label.toLowerCase().includes(search.toLowerCase()) || + ds.subtitle.toLowerCase().includes(search.toLowerCase()) + ); + + const visible = showAll ? filtered : filtered.slice(0, PAGE_SIZE); + const hiddenCount = filtered.length - PAGE_SIZE; + const hasMore = hiddenCount > 0; + + return ( + + { + setSearch(v); + setShowAll(false); + }} + /> + + No datasets found. + + {visible.map(ds => ( + { + setActiveOption(ds.key); + setInitStore(ds.store); + onOpenDescription(); + }} + className={`flex flex-col items-start gap-0.5 mb-2 cursor-pointer ${ + activeOption === ds.key ? 'bg-accent' : '' + }`} + > + {ds.label} + + {ds.subtitle} + + + ))} + + {!showAll && hasMore && ( + setShowAll(true)} + className="flex items-center justify-center gap-1.5 text-xs text-muted-foreground cursor-pointer py-2 border-t border-dashed border-border mt-1 hover:text-foreground" + > + + {hiddenCount} more dataset{hiddenCount > 1 ? 's' : ''} available + + )} + + {showAll && hasMore && ( + setShowAll(false)} + className="flex items-center justify-center gap-1.5 text-xs text-muted-foreground cursor-pointer py-2 border-t border-dashed border-border mt-1 hover:text-foreground" + > + + Show less + + )} + + + + ); +}; + +export default CuratedDatasets; \ No newline at end of file diff --git a/src/components/ui/MainPanel/DataSetsModal.tsx b/src/components/ui/MainPanel/DataSetsModal.tsx index 4dab59bf..4a70513b 100644 --- a/src/components/ui/MainPanel/DataSetsModal.tsx +++ b/src/components/ui/MainPanel/DataSetsModal.tsx @@ -5,33 +5,27 @@ import { useShallow } from 'zustand/shallow'; import { Dialog, DialogContent, - DialogHeader, - DialogDescription, DialogTitle, } from "@/components/ui/dialog"; -import { ButtonGroup } from "@/components/ui/button-group"; +import { + ButtonGroup, +} from "@/components/ui/button-group"; import { Button } from "@/components/ui/button-enhanced"; import { DescriptionContent } from './DescriptionContent'; -import StoreCatalog from './StoreCatalog'; -import { ZARR_CATALOG, ICECHUNK_CATALOG } from "@/assets/index"; +import CuratedDatasets from './CuratedDatasets'; import RemoteZarr from './RemoteZarr'; import LocalContent from './LocalContent'; import RemoteIcechunk from './RemoteIcechunk'; -type Tab = 'remote' | 'local' | 'icechunk'; +type Tab = 'curated' | 'remote' | 'local' | 'icechunk'; const TABS: { value: Tab; label: string }[] = [ - { value: 'remote', label: 'Remote Zarr' }, - { value: 'icechunk', label: 'Icechunk' }, - { value: 'local', label: 'Local' }, + { value: 'curated', label: 'Curated' }, + { value: 'remote', label: 'Remote Zarr' }, + { value: 'icechunk', label: 'Icechunk' }, + { value: 'local', label: 'Local' }, ]; -const TAB_DESCRIPTIONS: Record = { - remote: 'Browse and open remote Zarr stores via URL or from the catalog.', - icechunk: 'Connect to versioned Icechunk stores for transactional data access.', - local: 'Open a Zarr dataset stored on your local filesystem.', -}; - type Props = { open: boolean; onOpenChange: (v: boolean) => void; @@ -40,10 +34,8 @@ type Props = { const DatasetsModal = ({ open, onOpenChange, isSafari }: Props) => { const [activeOption, setActiveOption] = useState(''); - const [selectedUrl, setSelectedUrl] = useState(''); - const [selectedIcechunkUrl, setSelectedIcechunkUrl] = useState(''); const [hasFetched, setHasFetched] = useState(false); - const [activeTab, setActiveTab] = useState('remote'); + const [activeTab, setActiveTab] = useState('curated'); const { initStore, setInitStore, setOpenVariables, status } = useGlobalStore( useShallow(state => ({ @@ -55,46 +47,25 @@ const DatasetsModal = ({ open, onOpenChange, isSafari }: Props) => { ); const showDescription = hasFetched && status === null; + const openDescription = () => setHasFetched(true); const handleTabChange = (tab: Tab) => { setActiveTab(tab); setHasFetched(false); - setSelectedUrl(''); - setSelectedIcechunkUrl(''); - setActiveOption(''); - }; - - const handleOpenChange = (v: boolean) => { - if (!v) { - setSelectedUrl(''); - setSelectedIcechunkUrl(''); - setActiveOption(''); - setHasFetched(false); - } - onOpenChange(v); }; return ( - + - - Open dataset - {TAB_DESCRIPTIONS[activeTab]} - + Open Dataset + {/* Header */}
- {!showDescription && ( - <> -

- Open dataset -

-

- {TAB_DESCRIPTIONS[activeTab]} -

- - )} - +

+ Open dataset +

+ {TABS.map(({ value, label }) => (
-
+
+ {activeTab === 'curated' && ( + + )} {activeTab === 'remote' && ( - <> - - - + )} {activeTab === 'local' && ( { /> )} {activeTab === 'icechunk' && ( - <> - - - + )} {showDescription && ( { const [showStoreInput, setShowStoreInput] = useState(false); @@ -29,7 +26,6 @@ const Dataset = () => { const [popoverOpen, setPopoverOpen] = useState(false); const [isSafari, setIsSafari] = useState(false); const [openDatasetsModal, setOpenDatasetsModal] = useState(false); - const [useIcechunk, setUseIcechunk] = useState(false); const { initStore, setInitStore, setOpenVariables } = useGlobalStore( useShallow(state => ({ @@ -106,51 +102,36 @@ const Dataset = () => { className="cursor-pointer w-full justify-between gap-2 mb-1" onClick={() => setOpenDatasetsModal(true)} > - {popoverSide !== 'top' && } + {popoverSide !== 'top' && } Explore Datasets - {popoverSide === 'top' && } + {popoverSide === 'top' && } +
+
{ - const opening = !showStoreInput || activeOption !== 'remote'; - setShowStoreInput(opening); + setShowStoreInput(prev => !prev); setShowLocalInput(false); setActiveOption('remote'); setShowDescription(false); - if (opening) setUseIcechunk(false); }} > - Remote + Remote Zarr {showStoreInput && (
- setUseIcechunk(prev => !prev)} + setShowDescription(true)} /> - {useIcechunk ? ( - setShowDescription(true)} - /> - ) : ( - setShowDescription(true)} - /> - )}
)}
- -
{ setShowStoreInput(false); setActiveOption('local'); setShowDescription(false); - setUseIcechunk(false); }} > Local @@ -186,6 +166,7 @@ const Dataset = () => { />
)} +
diff --git a/src/components/ui/MainPanel/DatasetCollection.ts b/src/components/ui/MainPanel/DatasetCollection.ts new file mode 100644 index 00000000..fb17ba2d --- /dev/null +++ b/src/components/ui/MainPanel/DatasetCollection.ts @@ -0,0 +1,26 @@ +export const ZARR_STORES = { + ESDC: 'https://s3.bgc-jena.mpg.de:9000/esdl-esdc-v3.0.2/esdc-16d-2.5deg-46x72x1440-3.0.2.zarr', + SEASFIRE: 'https://s3.bgc-jena.mpg.de:9000/misc/seasfire_rechunked.zarr', + PRECIPITATION: 'https://storage.googleapis.com/cmip6/CMIP6/HighResMIP/EC-Earth-Consortium/EC-Earth3P-HR/highresSST-present/r1i1p1f1/Amon/pr/gr/v20170811/', +}; + +export const DATASETS_COLLECTION = [ + { + key: 'seasfire', + label: 'SeasFire Cube', + subtitle: 'A Global Dataset for Seasonal Fire Modeling in the Earth System', + store: ZARR_STORES.SEASFIRE, + }, + { + key: 'ESDC', + label: 'ESDC', + subtitle: 'Earth System Data Cube v3.0.2', + store: ZARR_STORES.ESDC, + }, + { + key: 'precipitation', + label: 'Precipitation (EC-Earth3P-HR)', + subtitle: 'Precipitation data from EC-Earth3P-HR highresSST-present', + store: ZARR_STORES.PRECIPITATION, + }, +]; \ No newline at end of file diff --git a/src/components/ui/MainPanel/LocalContent.tsx b/src/components/ui/MainPanel/LocalContent.tsx index 3d518c20..0c494420 100644 --- a/src/components/ui/MainPanel/LocalContent.tsx +++ b/src/components/ui/MainPanel/LocalContent.tsx @@ -40,6 +40,7 @@ const LocalContent = ({ {})} setOpenVariables={onOpenDescription} + setInitStore={setInitStore} /> )}
diff --git a/src/components/ui/MainPanel/LocalNetCDF.tsx b/src/components/ui/MainPanel/LocalNetCDF.tsx index fa9ee7d9..fe16b3cc 100644 --- a/src/components/ui/MainPanel/LocalNetCDF.tsx +++ b/src/components/ui/MainPanel/LocalNetCDF.tsx @@ -3,14 +3,13 @@ import React, {ChangeEvent, useState} from 'react' import { Input } from '../input' import { useGlobalStore } from '@/GlobalStates/GlobalStore'; import { loadNetCDF, NETCDF_EXT_REGEX } from '@/utils/loadNetCDF'; -import { saveFile } from '@/utils/IndexDB'; + import { Alert, AlertDescription, AlertTitle, } from '@/components/ui/alert'; import { isMobile } from '../MobileUIHider'; -import { useZarrStore } from '@/GlobalStates/ZarrStore'; interface LocalNCType { setOpenVariables: (open: boolean) => void; @@ -21,26 +20,23 @@ const LocalNetCDF = ({ setOpenVariables}:LocalNCType) => { // const {ncModule} = useZarrStore.getState() const [ncError, setError] = useState(null); - const handleFileSelect = async (event: ChangeEvent) => { - setError(null); - const files = event.target.files; - if (!files || files.length === 0) { setStatus(null); return; } - const file = files[0]; - if (!NETCDF_EXT_REGEX.test(file.name)) { - setError('Please select a valid NetCDF (.nc, .netcdf, .nc3, .nc4) file.'); - return; - } - try { - await loadNetCDF(file, file.name); - const blobKey = `local_${file.name}` - await saveFile(file, blobKey) - useZarrStore.setState({blobKey}) - console.log('Set Key?') - setOpenVariables(true) - } catch (e) { - setError(`Failed to load file: ${e instanceof Error ? e.message : String(e)}`); - } - }; + const handleFileSelect = async (event: ChangeEvent) => { + setError(null); + const files = event.target.files; + if (!files || files.length === 0) { setStatus(null); return; } + const file = files[0]; + if (!NETCDF_EXT_REGEX.test(file.name)) { + setError('Please select a valid NetCDF (.nc, .netcdf, .nc3, .nc4) file.'); + return; + } + try { + await loadNetCDF(file, file.name); + setOpenVariables(true) + + } catch (e) { + setError(`Failed to load file: ${e instanceof Error ? e.message : String(e)}`); + } + }; return (
diff --git a/src/components/ui/MainPanel/LocalZarr.tsx b/src/components/ui/MainPanel/LocalZarr.tsx index 216ecbd9..f5404046 100644 --- a/src/components/ui/MainPanel/LocalZarr.tsx +++ b/src/components/ui/MainPanel/LocalZarr.tsx @@ -5,16 +5,25 @@ import { useZarrStore } from '@/GlobalStates/ZarrStore'; import { useGlobalStore } from '@/GlobalStates/GlobalStore'; import { Input } from '../input'; import ZarrParser from '@/components/zarr/ZarrParser'; -import { saveFile } from '@/utils/IndexDB'; interface LocalZarrType { setShowLocal: React.Dispatch>; setOpenVariables: (open: boolean) => void; + setInitStore: (store: string) => void; } -export async function LoadLocalZarr(files:File[]){ - // Create a Map to hold the Zarr store data +const LocalZarr = ({setShowLocal, setOpenVariables, setInitStore}:LocalZarrType) => { + const setCurrentStore = useZarrStore(state => state.setCurrentStore) + const {setStatus} = useGlobalStore.getState() + const handleFileSelect = async (event: ChangeEvent) => { + const files = event.target.files; + if (!files || files.length === 0) { + setStatus(null) + return; + } + // Create a Map to hold the Zarr store data const fileMap = new Map(); + // The base directory name will be the first part of the relative path const baseDir = files[0].webkitRelativePath.split('/')[0]; @@ -26,6 +35,7 @@ export async function LoadLocalZarr(files:File[]){ fileMap.set('/' + relativePath, file); // Zarrita looks for a leading slash before variables. Need to add it back } } + // Create a custom zarrita store from the Map const customStore: zarr.AsyncReadable = { async get(key: string): Promise { @@ -38,52 +48,37 @@ export async function LoadLocalZarr(files:File[]){ // Open the Zarr store using the custom store let store = await zarr.withMaybeConsolidatedMetadata(customStore); if (!('contents' in store)){ - console.log("Trying to parse?") // Metadata is missing. We will need to parse variables here. store = await ZarrParser(files, customStore) } const gs = await zarr.open(store, {kind: 'group'}); - const blobKey = `local_${baseDir}` - useGlobalStore.setState({initStore:blobKey}) - useZarrStore.setState({ useNC: false, blobKey, currentStore:gs}) + setCurrentStore(gs); + setShowLocal(false); + setOpenVariables(true); + setInitStore(`local_${baseDir}`) + setStatus(null) + useZarrStore.setState({ useNC: false}) } catch (error) { - + setStatus(null) if (error instanceof Error) { console.log(`Error opening Zarr store: ${error.message}`); } else { console.log('An unknown error occurred when opening the Zarr store.'); } } -} - -const LocalZarr = ({setShowLocal, setOpenVariables}:LocalZarrType) => { - const {setStatus} = useGlobalStore.getState() - const handleFileSelect = async (event: ChangeEvent) => { - const files = event.target.files; - if (!files || files.length === 0) { - setStatus(null) - return; - } - const baseDir = files[0].webkitRelativePath.split('/')[0]; - LoadLocalZarr(Array.from(files)).then(()=>{ - saveFile(Array.from(files), `local_${baseDir}`) - setShowLocal(false); - setOpenVariables(true); - setStatus(null) - }) - }; - return ( -
- -
- ) + }; + return ( +
+ +
+ ) } export default LocalZarr diff --git a/src/components/ui/MainPanel/RemoteIcechunk.tsx b/src/components/ui/MainPanel/RemoteIcechunk.tsx index 1abbe615..0497f112 100644 --- a/src/components/ui/MainPanel/RemoteIcechunk.tsx +++ b/src/components/ui/MainPanel/RemoteIcechunk.tsx @@ -93,14 +93,10 @@ const HeaderRows = ({ rows, set }: HeaderRowsProps) => { type Props = { setInitStore: (v: string) => void; onOpenDescription: () => void; - selectedUrl?: string }; -const RemoteIcechunk = ({ setInitStore, onOpenDescription, selectedUrl = '' }: Props) => { - const [url, setUrl] = useState(selectedUrl); - - const [showSettings, setShowSettings] = useState(false); - +const RemoteIcechunk = ({ setInitStore, onOpenDescription }: Props) => { + const [url, setUrl] = useState(''); const [refType, setRefType] = useState('branch'); const [refValue, setRefValue] = useState('main'); @@ -156,7 +152,7 @@ const RemoteIcechunk = ({ setInitStore, onOpenDescription, selectedUrl = '' }: P
{/* URL + Fetch */} -
+
- {/* Settings toggle */} + {/* Branch / Tag / Snapshot */} +
+
+ {REF_TABS.map(({ value, label }) => ( + + ))} +
+ setRefValue(e.target.value)} + /> +
+ + {/* Storage options */}
+ {showStorage && ( +
+
- {showSettings && ( -
- - {/* Branch / Tag / Snapshot */} -
-
- {REF_TABS.map(({ value, label }) => ( - - {showStorage && ( -
-
- - {/* Credentials Select */} -
- Credentials - -
- - {/* Cache Select */} -
- Cache - -
+ + + + + + Default + {CREDENTIALS_OPTIONS.map(o => ( + {o.label} + ))} + + + +
-
- + {/* Cache Select */} +
+ Cache +
- )} -
- {/* fetchClient headers */} -
- - {showFetchClientHeaders && ( - - )} +
+
+ )} +
- {/* Advanced */} -
- - {showAdvanced && ( -
-
- - setMaxRetries(Math.max(0, parseInt(e.target.value, 10) || 0))} - /> -
-
- - setRetryDelay(Math.max(0, parseInt(e.target.value, 10) || 0))} - /> -
-
- )} + {/* fetchClient headers */} +
+ + {showFetchClientHeaders && ( + + )} +
+ + {/* Advanced */} +
+ + {showAdvanced && ( +
+
+ + setMaxRetries(Number(e.target.value))} + /> +
+
+ + setRetryDelay(Number(e.target.value))} + /> +
-
)}
+
); }; diff --git a/src/components/ui/MainPanel/RemoteZarr.tsx b/src/components/ui/MainPanel/RemoteZarr.tsx index 05e93b5e..bc891947 100644 --- a/src/components/ui/MainPanel/RemoteZarr.tsx +++ b/src/components/ui/MainPanel/RemoteZarr.tsx @@ -3,6 +3,7 @@ import React, { useState } from 'react'; import { useGlobalStore } from '@/GlobalStates/GlobalStore'; import { Input } from '@/components/ui/'; import { Button } from '@/components/ui/button-enhanced'; + import { ChevronDown, ChevronUp, Plus, Trash2 } from 'lucide-react'; import { useZarrStore } from '@/GlobalStates/ZarrStore'; @@ -10,10 +11,10 @@ type HeaderRow = { key: string; value: string }; type AuthPreset = 'none' | 'bearer' | 'basic' | 'apikey'; const PRESETS: { value: AuthPreset; label: string }[] = [ - { value: 'none', label: 'None' }, - { value: 'bearer', label: 'Bearer' }, - { value: 'basic', label: 'Basic' }, - { value: 'apikey', label: 'API Key' }, + { value: 'none', label: 'None' }, + { value: 'bearer', label: 'Bearer' }, + { value: 'basic', label: 'Basic' }, + { value: 'apikey', label: 'API Key' }, ]; const PRESET_KEYS: Record, string> = { @@ -32,16 +33,14 @@ type Props = { initStore: string; setInitStore: (v: string) => void; onOpenDescription: () => void; - selectedUrl?: string; }; -const RemoteZarr = ({ initStore, setInitStore, onOpenDescription, selectedUrl = '' }: Props) => { +const RemoteZarr = ({ initStore, setInitStore, onOpenDescription }: Props) => { const [showFetchOptions, setShowFetchOptions] = useState(false); const [preset, setPreset] = useState('none'); const [presetValue, setPresetValue] = useState(''); const [headers, setHeaders] = useState([{ key: '', value: '' }]); const [showCustom, setShowCustom] = useState(false); - const [urlValue, setUrlValue] = useState(selectedUrl); const addHeaderRow = () => setHeaders(h => [...h, { key: '', value: '' }]); const removeHeaderRow = (i: number) => setHeaders(h => h.filter((_, idx) => idx !== i)); @@ -74,16 +73,17 @@ const RemoteZarr = ({ initStore, setInitStore, onOpenDescription, selectedUrl = className="flex flex-col gap-3" onSubmit={(e: React.FormEvent) => { e.preventDefault(); - if (!urlValue) return; + const input = e.currentTarget.elements[0] as HTMLInputElement; + const url = input.value; + if (!url) return; const fetchHandler = buildFetchHandler(); - if (fetchHandler && urlValue.startsWith('http://')) { + if (fetchHandler && url.startsWith('http://')) { useGlobalStore.getState().setStatus('Error: Cannot send auth headers over plain HTTP — use HTTPS.'); return; } - const fetchOptions = { - ...(fetchHandler && { fetch: fetchHandler }), + ...(fetchHandler && { fetch: fetchHandler}), }; useZarrStore.getState().setIcechunkOptions(null); @@ -93,24 +93,19 @@ const RemoteZarr = ({ initStore, setInitStore, onOpenDescription, selectedUrl = useGlobalStore.getState().setStatus('Fetching...'); useZarrStore.getState().bumpFetchKey(); - setInitStore(urlValue); + setInitStore(url); onOpenDescription(); }} > {/* URL + Fetch */} -
- setUrlValue(e.target.value)} - /> +
+
- {/* fetchOptions toggle */} + {/* FetchOptions */}
-
diff --git a/src/components/ui/NavBar/ParameterExport.tsx b/src/components/ui/NavBar/ParameterExport.tsx deleted file mode 100644 index 446206fc..00000000 --- a/src/components/ui/NavBar/ParameterExport.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import React, { useState } from 'react' -import { useGlobalStore } from '@/GlobalStates/GlobalStore' -import { usePlotStore } from '@/GlobalStates/PlotStore' -import { useZarrStore } from '@/GlobalStates/ZarrStore' -import { BiExport } from "react-icons/bi"; -import { FiCopy } from "react-icons/fi"; -import { Popover, PopoverTrigger, PopoverContent } from "@/components/ui/popover" -import { Button } from '../button'; - -function pick(obj: Record, keys: string[]) { - return Object.fromEntries( - keys.map((k) => [k, obj[k]]) - ) -} -const globalValues = [ - 'initStore', - 'storeFromURL', - 'variable', -] - -const plotValues = [ - 'plotType', - 'pointSize', - 'scalePoints', - 'scaleIntensity', - 'timeScale', - 'valueRange', - 'xRange', - 'yRange', - 'zRange', - 'showPoints', - 'linePointSize', - 'lineWidth', - 'lineColor', - 'pointColor', - 'useLineColor', - 'lineResolution', - 'cOffset', - 'cScale', - 'useFragOpt', - 'useCustomColor', - 'useCustomPointColor', - 'transparency', - 'nanTransparency', - 'nanColor', - 'showBorders', - 'borderColor', - 'lonExtent', - 'latExtent', - 'originalExtent', - 'lonResolution', - 'latResolution', - 'colorIdx', - 'vTransferRange', - 'vTransferScale', - 'sphereResolution', - 'displacement', - 'displaceSurface', - 'offsetNegatives', - 'zSlice', - 'ySlice', - 'xSlice', - 'interpPixels', - 'useOrtho', - 'rotateFlat', - 'fillValue', - 'coarsen', - 'kernel', - 'useBorderTexture', - 'maskValue', - 'borderWidth', - 'cameraPosition', - 'disablePointScale', -] - -const zarrValues = [ - 'zSlice', - 'ySlice', - 'xSlice', - 'compress', - 'useNC', // This one is more static and so toggling switch doesn't break all other logic - 'fetchNC', - 'coarsen', - 'kernelSize', - 'kernelDepth', - 'icechunkOptions', - 'fetchOptions', - 'fetchKey', - 'blobKey' -] - - -export const ParameterExport = () => { - const [copied, setCopied] = useState(false); - - function generateURL(){ - const {camera} = usePlotStore.getState() - usePlotStore.setState({cameraPosition:camera?.position}) // Set Camera position first to copy visual state - const fullObj = { - globalState: pick(useGlobalStore.getState(), globalValues), - plotState: pick(usePlotStore.getState(), plotValues), - zarrState: pick(useZarrStore.getState(), zarrValues), - } - const jString = JSON.stringify(fullObj, (_, v) => typeof v === 'bigint' ? v.toString() : v) - const params = `https://browzarr.io/latest/?data=${encodeURIComponent(jString)}` - return params - } - - const copyToClipboard = async () => { - const url = generateURL() - await navigator.clipboard.writeText(url); - setCopied(true); - setTimeout(() => setCopied(false), 1500); //Use for a pop-up that fades away - }; - - return ( - - - - - -
- -
- Copied! -
-
-
-
- ) -} - - diff --git a/src/components/ui/Widgets/Switcher.tsx b/src/components/ui/Widgets/Switcher.tsx index b066e0ab..e5c1caee 100644 --- a/src/components/ui/Widgets/Switcher.tsx +++ b/src/components/ui/Widgets/Switcher.tsx @@ -4,7 +4,7 @@ export const Switcher = ({leftText, rightText, state, onClick, className} : {lef return (
@@ -14,7 +14,7 @@ export const Switcher = ({leftText, rightText, state, onClick, className} : {lef {rightText}
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index 4d38506c..d31ae39a 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -13,7 +13,7 @@ const buttonVariants = cva( destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40", outline: - "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50", + "border bg-background shadow-xs hover:bg-accent cursor-pointer hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: diff --git a/src/components/zarr/ZarrParser.ts b/src/components/zarr/ZarrParser.ts index a8cdef0d..74b47475 100644 --- a/src/components/zarr/ZarrParser.ts +++ b/src/components/zarr/ZarrParser.ts @@ -11,7 +11,6 @@ function is_v3(meta: any) { return "zarr_format" in meta && meta.zarr_format === 3; } async function ZarrParser(files: any, store: any){ - console.log(store) const fileCount = files.length; const vars = [] const metadata: { [key: string]: any } = {} @@ -27,7 +26,6 @@ async function ZarrParser(files: any, store: any){ } for (const variable of vars){ const decoded = await store.get(variable) - console.log(decoded) metadata[variable.slice(1)] = jsonDecodeObject(decoded) } const v2_meta = {metadata, zarr_consolidated_format: 1} diff --git a/src/utils/HelperFuncs.ts b/src/utils/HelperFuncs.ts index e3df047a..85dfc120 100644 --- a/src/utils/HelperFuncs.ts +++ b/src/utils/HelperFuncs.ts @@ -402,4 +402,8 @@ export function calculateStrides( return shape.reduce((a: number, b: number, i: number) => a * (i > idx ? b : 1), 1) }) return newStrides +} + +export function permuteArr(arr: any[], permute?:number[]) { + return permute ? permute.map(i => arr[i]) : arr; } \ No newline at end of file diff --git a/src/utils/IndexDB.ts b/src/utils/IndexDB.ts deleted file mode 100644 index e48ad0ca..00000000 --- a/src/utils/IndexDB.ts +++ /dev/null @@ -1,32 +0,0 @@ -const DB_NAME = 'browzarr-files'; -const STORE = 'blobs'; - - -export function openDB(): Promise { - return new Promise((res, rej) => { - const req = indexedDB.open(DB_NAME, 1); - req.onupgradeneeded = () => req.result.createObjectStore(STORE); - req.onsuccess = () => res(req.result); - req.onerror = () => rej(req.error); - }); -} - -export async function saveFile(blob: any, key: string): Promise { - const db = await openDB(); - return new Promise((res, rej) => { - const tx = db.transaction(STORE, 'readwrite'); - tx.objectStore(STORE).put({ blob, key }, key); - tx.oncomplete = () => res(key); - tx.onerror = () => rej(tx.error); - }); -} - -export async function loadFile(key: string): Promise<{ blob: any; name: string } | null> { - const db = await openDB(); - return new Promise((res, rej) => { - const tx = db.transaction(STORE, 'readonly'); - const req = tx.objectStore(STORE).get(key); - req.onsuccess = () => res(req.result ?? null); - req.onerror = () => rej(req.error); - }); -} \ No newline at end of file