Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
18c5c77
support for int8-datasets
Karinon Mar 9, 2026
2f67841
fixed issues with one specific dataset
Karinon Mar 9, 2026
3cce878
full-height control panel
Karinon Mar 10, 2026
681d09f
intermediate state
Karinon Mar 11, 2026
93b8822
some new design choices
Karinon Mar 11, 2026
9bf43b0
some cleanups and better bound-management
Karinon Mar 12, 2026
38fc5d1
fix linter issues
Karinon Mar 12, 2026
e2df9e2
added visible colormaps inside the selector. Had to use primevue for …
Karinon Mar 12, 2026
f293d67
fixed webgl-warning (again) and added additional names to the infopan…
Karinon Mar 13, 2026
904216c
remove the shifting to the right
Karinon Mar 13, 2026
ad6eeb6
auto-fit projection to screen
Karinon Mar 13, 2026
429d96a
added rotation to 2D-Projections
Karinon Mar 13, 2026
64c694e
darkmode adjustments
Karinon Mar 13, 2026
d0aecdf
preview on hover
Karinon Mar 13, 2026
ab7b68e
hopefully repaired animation issues with the dist-plot
Karinon Mar 16, 2026
01c8a1a
* moved zarr-format-version into the data types-block as it has nothi…
Karinon Mar 17, 2026
b8cd11d
added new snapshot functionality
Karinon Mar 17, 2026
7d42135
fixed linter issues
Karinon Mar 17, 2026
5a2bfbf
some smaller fixes
Karinon Mar 17, 2026
26fc1bd
linter warnings
Karinon Mar 17, 2026
e019ce3
removed the word bounds
Karinon Mar 17, 2026
669df42
added graticules and keyboard shortcuts
Karinon Mar 17, 2026
44187aa
repair broken scroll
Karinon Mar 17, 2026
95d5148
graticules button
Karinon Mar 17, 2026
e37da3e
adressed a few copilot-comments
Karinon Mar 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest

container:
image: node:20
image: node:24

steps:
- name: Checkout repository
Expand Down
2 changes: 1 addition & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ export default [
{ from: "store", allow: ["lib", "utils"] },
{ from: "lib", allow: ["assets", "utils"] },
{ from: "views", allow: ["lib", "store", "ui", "utils"] },
{ from: "src", allow: ["lib", "src", "router", "views"] },
{ from: "src", allow: ["lib", "src", "router", "views", "utils"] },
{ from: "router", allow: ["views"] },
{ from: "utils", disallow: ["*"] },
],
Expand Down
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@vueuse/core": "^14.0.0",
"axios": "^1.13.6",
"bulma": "^1.0.4",
"canvas-txt": "^4.1.1",
"chart.js": "^4.5.1",
"chartjs-plugin-annotation": "^3.1.0",
"d3-delaunay": "^6.0.4",
Expand Down
Binary file added public/static/colormaps/Blues.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/static/colormaps/BrBG.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/static/colormaps/BuGn.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/static/colormaps/BuPu.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/static/colormaps/GnBu.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/static/colormaps/Greens.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/static/colormaps/Greys.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/static/colormaps/OrRd.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/static/colormaps/Oranges.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/static/colormaps/PRGn.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/static/colormaps/PiYG.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/static/colormaps/PuBu.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/static/colormaps/PuBuGn.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/static/colormaps/PuOr.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/static/colormaps/PuRd.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/static/colormaps/Purples.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/static/colormaps/RdBu.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/static/colormaps/RdGy.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/static/colormaps/RdPu.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/static/colormaps/RdYlBu.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/static/colormaps/RdYlGn.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/static/colormaps/Reds.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/static/colormaps/Spectral.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/static/colormaps/YlGn.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/static/colormaps/YlGnBu.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/static/colormaps/YlOrBr.webp
Binary file added public/static/colormaps/YlOrRd.webp
Binary file added public/static/colormaps/algae.webp
Binary file added public/static/colormaps/amp.webp
Binary file added public/static/colormaps/balance.webp
Binary file added public/static/colormaps/bwr.webp
Binary file added public/static/colormaps/cividis.webp
Binary file added public/static/colormaps/coolwarm.webp
Binary file added public/static/colormaps/curl.webp
Binary file added public/static/colormaps/deep.webp
Binary file added public/static/colormaps/delta.webp
Binary file added public/static/colormaps/dense.webp
Binary file added public/static/colormaps/diff.webp
Binary file added public/static/colormaps/gray.webp
Binary file added public/static/colormaps/haline.webp
Binary file added public/static/colormaps/hpluv.webp
Binary file added public/static/colormaps/hsluv.webp
Binary file added public/static/colormaps/ice.webp
Binary file added public/static/colormaps/inferno.webp
Binary file added public/static/colormaps/magma.webp
Binary file added public/static/colormaps/matter.webp
Binary file added public/static/colormaps/phase.webp
Binary file added public/static/colormaps/plasma.webp
Binary file added public/static/colormaps/rain.webp
Binary file added public/static/colormaps/seismic.webp
Binary file added public/static/colormaps/solar.webp
Binary file added public/static/colormaps/speed.webp
Binary file added public/static/colormaps/tarn.webp
Binary file added public/static/colormaps/tempo.webp
Binary file added public/static/colormaps/thermal.webp
Binary file added public/static/colormaps/turbid.webp
Binary file added public/static/colormaps/turbo.webp
Binary file added public/static/colormaps/viridis.webp
1 change: 1 addition & 0 deletions public/static/ne_50m_graticules_30.geojson
Diff not rendered.
117 changes: 117 additions & 0 deletions scripts/generate_colormap_swatches.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#!/usr/bin/env python3
"""
Generate WebP gradient swatches for every colormap defined in
src/lib/shaders/colormapShaders.ts and write them to
public/static/colormaps/<name>.webp

The GLSL polynomial form is a degree-6 Horner evaluation identical across all
colormaps:
color = c0 + t*(c1 + t*(c2 + t*(c3 + t*(c4 + t*(c5 + t*c6)))))
"""

import pathlib
import re
import sys

try:
from PIL import Image
except ImportError:
sys.exit("Pillow is required: pip install Pillow")

# ---------------------------------------------------------------------------
# Paths
# ---------------------------------------------------------------------------

ROOT = pathlib.Path(__file__).parent.parent
SHADER_SRC = ROOT / "src" / "lib" / "shaders" / "colormapShaders.ts"
OUT_DIR = ROOT / "public" / "static" / "colormaps"

SWATCH_W = 256
SWATCH_H = 16

# ---------------------------------------------------------------------------
# Parse polynomial coefficients from the TypeScript / GLSL source
# ---------------------------------------------------------------------------


def parse_colormaps(src: str) -> dict[str, list[list[float]]]:
"""Return {name: [[r0,g0,b0], [r1,g1,b1], ..., [r6,g6,b6]]}"""
colormaps: dict[str, list[list[float]]] = {}

# Match every 'vec3 <name>(float t) { ... }' block
func_re = re.compile(r"vec3\s+(\w+)\s*\(float\s+t\)\s*\{([^}]+)\}", re.DOTALL)
# Match 'const vec3 coeffsN = vec3(r, g, b);'
coeff_re = re.compile(
r"const\s+vec3\s+coeffs\d+\s*=\s*vec3\s*\(\s*"
r"([+-]?\d+\.?\d*(?:e[+-]?\d+)?)\s*,\s*"
r"([+-]?\d+\.?\d*(?:e[+-]?\d+)?)\s*,\s*"
r"([+-]?\d+\.?\d*(?:e[+-]?\d+)?)\s*\)"
)

for m in func_re.finditer(src):
name = m.group(1)
body = m.group(2)
coeffs = [[float(c) for c in cm.groups()] for cm in coeff_re.finditer(body)]
if len(coeffs) == 7:
colormaps[name] = coeffs

return colormaps


# ---------------------------------------------------------------------------
# Evaluate polynomial (Horner's method, matches GLSL exactly)
# ---------------------------------------------------------------------------


def eval_colormap(coeffs: list[list[float]], t: float) -> tuple[int, int, int]:
c = coeffs
r = c[0][0] + t * (
c[1][0]
+ t * (c[2][0] + t * (c[3][0] + t * (c[4][0] + t * (c[5][0] + t * c[6][0]))))
)
g = c[0][1] + t * (
c[1][1]
+ t * (c[2][1] + t * (c[3][1] + t * (c[4][1] + t * (c[5][1] + t * c[6][1]))))
)
b = c[0][2] + t * (
c[1][2]
+ t * (c[2][2] + t * (c[3][2] + t * (c[4][2] + t * (c[5][2] + t * c[6][2]))))
)
# clamp to [0, 1] and convert to uint8 (matches GLSL clamp)
ri = max(0, min(255, round(r * 255)))
gi = max(0, min(255, round(g * 255)))
bi = max(0, min(255, round(b * 255)))
return (ri, gi, bi)


# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------


def main() -> None:
src = SHADER_SRC.read_text()
colormaps = parse_colormaps(src)

if not colormaps:
sys.exit(f"No colormap functions found in {SHADER_SRC}")

OUT_DIR.mkdir(parents=True, exist_ok=True)

for name, coeffs in sorted(colormaps.items()):
img = Image.new("RGB", (SWATCH_W, SWATCH_H))
pixels = img.load()
for x in range(SWATCH_W):
t = x / (SWATCH_W - 1)
color = eval_colormap(coeffs, t)
for y in range(SWATCH_H):
pixels[x, y] = color # type: ignore[index]

out_path = OUT_DIR / f"{name}.webp"
img.save(out_path, "WEBP", lossless=True, quality=100)

print(f"Generated {len(colormaps)} swatches → {OUT_DIR}")


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions src/assets/base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
--bulma-scheme-main-bis: hsl(220, 40%, 20%);
--bulma-scheme-main-ter: hsl(220, 35%, 23%);
--bulma-body-color: hsl(210, 10%, 80%);
--bulma-text: hsl(210, 10%, 82%);
}
}

Expand Down
1 change: 1 addition & 0 deletions src/lib/data/zarrUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,7 @@ export function castDataVarToFloat32(
rawData instanceof Float64Array ||
rawData instanceof Int32Array ||
rawData instanceof Int16Array ||
rawData instanceof Int8Array ||
rawData instanceof Uint16Array ||
rawData instanceof Uint8Array
) {
Expand Down
14 changes: 14 additions & 0 deletions src/lib/types/GlobeTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,20 @@ export type TSources = {
}[];
};

export type TSnapshotBackground = "black" | "white" | "transparent";

export type TSnapshotOptions = {
background: TSnapshotBackground;
showDatasetInfo: boolean;
showColormap: boolean;
};

export const DEFAULT_SNAPSHOT_OPTIONS: TSnapshotOptions = {
background: "black",
showDatasetInfo: true,
showColormap: true,
};

export type TZarrV3RootMetadata = {
zarr_format: 3;
node_type: "group";
Expand Down
27 changes: 26 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { definePreset } from "@primevue/themes";
import Aura from "@primevue/themes/aura";
import { createPinia } from "pinia";
import PrimeVue from "primevue/config";
Expand All @@ -6,14 +7,38 @@ import { createApp } from "vue";

import App from "./App.vue";
import router from "./router";
import { vWordBreak } from "./utils/wordbreak";
const app = createApp(App);
app.directive("word-break", vWordBreak);

const pinia = createPinia();

const MyPreset = definePreset(Aura, {
semantic: {
primary: {
50: "var(--bulma-info-95)",
100: "var(--bulma-info-90)",
200: "var(--bulma-info-80)",
300: "var(--bulma-info-70)",
400: "var(--bulma-info-60)",
500: "var(--bulma-info-50)",
600: "var(--bulma-info-40)",
700: "var(--bulma-info-30)",
800: "var(--bulma-info-20)",
900: "var(--bulma-info-10)",
950: "var(--bulma-info-05)",
},
},
components: {
select: {},
},
});

app.use(router);
app.use(pinia);
app.use(PrimeVue, {
theme: {
preset: Aura,
preset: MyPreset,
},
});
app.use(ToastService);
Expand Down
9 changes: 9 additions & 0 deletions src/store/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const useGlobeControlStore = defineStore("globeControl", {
state: () => {
return {
showCoastLines: true,
showGraticules: false,
// simplified UI choice (Off|Sea|Land|Globe) — used by controls
landSeaMaskChoice: LAND_SEA_MASK_MODES.OFF as TLandSeaMaskMode,
// when true, use the textured versions; when false, use the greyscale/solid versions
Expand All @@ -43,14 +44,22 @@ export const useGlobeControlStore = defineStore("globeControl", {
dimSlidersDisplay: [] as (number | null)[],
isInitializingVariable: false,
controlPanelVisible: true,
datasetTitle: "" as string,
projectionMode: PROJECTION_TYPES.NEARSIDE_PERSPECTIVE as TProjectionType,
projectionCenter: { lat: 0, lon: 0 } as TProjectionCenter,
isRotating: false,
};
},
actions: {
toggleRotating() {
this.isRotating = !this.isRotating;
},
toggleCoastLines() {
this.showCoastLines = !this.showCoastLines;
},
toggleGraticules() {
this.showGraticules = !this.showGraticules;
},
startLoading() {
this.loading = true;
},
Expand Down
74 changes: 46 additions & 28 deletions src/ui/grids/Curvilinear.vue
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,42 @@ function projectCellVertices(
return currentOffset;
}

// Skip cells whose coordinate corners contain NaN/Infinity – projecting
// such values (especially on the globe) produces NaN positions that
// break THREE.js's computeBoundingSphere. The pre-allocated zeros in
// positionValues / latLonValues are valid fallbacks, and setting the
// data value to NaN causes the fragment shader to discard the fragment.
function fillCellPositionAndData(
latPoints: number[],
lonPoints: number[],
data: Float32Array,
idx00: number,
positionValues: Float32Array,
dataValues: Float32Array,
latLonValues: Float32Array,
positionOffset: number,
cellIndex: number
): number {
const hasInvalidCoords =
latPoints.some((v) => !Number.isFinite(v)) ||
lonPoints.some((v) => !Number.isFinite(v));
if (hasInvalidCoords) {
positionOffset += 12; // 4 vertices × 3 coords, already zeroed
dataValues.fill(NaN, cellIndex * 4, cellIndex * 4 + 4);
} else {
positionOffset = projectCellVertices(
latPoints,
lonPoints,
positionValues,
latLonValues,
positionOffset,
cellIndex
);
dataValues.fill(data[idx00], cellIndex * 4, cellIndex * 4 + 4);
}
return positionOffset;
}

function buildBatchGeometryData(
latitudes: Float64Array,
longitudes: Float64Array,
Expand All @@ -393,56 +429,38 @@ function buildBatchGeometryData(
const { positionValues, dataValues, latLonValues, indices } =
initializeArrays(jEnd, jStart, ni);

let positionOffset = 0; // Offset into positions array (increments by 12 per cell)
let idxOffset = 0; // Offset into indices array (increments by 6 per cell)
let cellIndex = 0; // Current cell number (used for vertex indexing)
let positionOffset = 0;
let idxOffset = 0;
let cellIndex = 0;

// Main loop: iterate through grid cells in this batch
// j goes from jStart to jEnd-1 (we need j+1 to exist for each cell)
// i goes from 0 to ni-1 (full width, with wraparound for last column)
for (let j = jStart; j < jEnd; j++) {
for (let i = 0; i < ni; i++) {
// Convert 2D grid coordinates (j,i) to 1D array indices
// The 2D arrays are flattened in row-major order: index = j * ni + i
// Calculate indices for the 4 corners of this grid cell:
// Handle longitude ordering based on flip flag
const { idx00, idx01, idx10, idx11 } = getCellCornerIndices(
j,
i,
ni,
flipLongitude
);

const { latPoints, lonPoints } = extractCellCorners(
{ idx00, idx01, idx10, idx11 },
latitudes,
longitudes
);

positionOffset = projectCellVertices(
positionOffset = fillCellPositionAndData(
latPoints,
lonPoints,
data,
idx00,
positionValues,
dataValues,
latLonValues,
positionOffset,
cellIndex
);

// Assign data value to all 4 vertices of this cell
// We use the data value from the bottom-left corner (idx00)
dataValues.fill(data[idx00], cellIndex * 4, cellIndex * 4 + 4);

// Create triangle indices to form a quad from our 4 vertices
// Each quad is split into 2 triangles:
// Triangle 1: vertices 0, 1, 2 (bottom-left, bottom-right, top-right)
// Triangle 2: vertices 0, 2, 3 (bottom-left, top-right, top-left)
// This creates counter-clockwise winding for proper rendering
const v = cellIndex * 4; // Base vertex index for this cell
const v = cellIndex * 4;
indices.set([v, v + 1, v + 2, v, v + 2, v + 3], idxOffset);

// Move to next position in arrays for the next cell
idxOffset += 6; // 2 triangles × 3 indices = 6 indices
cellIndex++; // Increment cell counter
idxOffset += 6;
cellIndex++;
}
}

Expand Down
Loading
Loading