Bin
2025-12-17 611bfe34c3c96199eaaf6cf9e41a75892e44e879
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import colormap from "colormap";
import { clamp } from "../Common/Utils";
 
// Define known color schemes
export const COLOR_SCHEMES = {
  JET: "jet",
  HSV: "hsv",
  HOT: "hot",
  COOL: "cool",
  SPRING: "spring",
  SUMMER: "summer",
  AUTUMN: "autumn",
  WINTER: "winter",
  BONE: "bone",
  COPPER: "copper",
  GREYS: "greys",
  YIGNBU: "YIGnBu",
  GREENS: "greens",
  YIORRD: "YIOrRd",
  BLUERED: "bluered",
  RDBU: "RdBu",
  PICNIC: "picnic",
  RAINBOW: "rainbow",
  PORTLAND: "portland",
  BLACKBODY: "blackbody",
  EARTH: "earth",
  ELECTRIC: "electric",
  VIRIDIS: "viridis",
  INFERNO: "inferno",
  MAGMA: "magma",
  PLASMA: "plasma",
  WARM: "warm",
  RAINBOW_SOFT: "rainbow-soft",
  BATHYMETRY: "bathymetry",
  CDOM: "cdom",
  CHLOROPHYLL: "chlorophyll",
  DENSITY: "density",
  FREESURFACE_BLUE: "freesurface-blue",
  FREESURFACE_RED: "freesurface-red",
  OXYGEN: "oxygen",
  PAR: "par",
  PHASE: "phase",
  SALINITY: "salinity",
  TEMPERATURE: "temperature",
  TURBIDITY: "turbidity",
  VELOCITY_BLUE: "velocity-blue",
  VELOCITY_GREEN: "velocity-green",
  CUBEHELIX: "cubehelix",
} as const;
 
export type ColorScheme = (typeof COLOR_SCHEMES)[keyof typeof COLOR_SCHEMES];
 
// Define the number of shades for cached colormaps
const COLORMAP_SHADES = 512;
 
export class ColorMapper {
  private activeColormap: number[][] = [];
  private currentScheme: ColorScheme;
 
  constructor(initialScheme: ColorScheme = COLOR_SCHEMES.VIRIDIS) {
    this.currentScheme = initialScheme;
    this.cacheColormap();
  }
 
  /**
   * Update the color scheme and regenerate the cache
   */
  public setColorScheme(schemeName: ColorScheme): void {
    if (this.currentScheme !== schemeName) {
      this.currentScheme = schemeName;
      this.cacheColormap();
    }
  }
 
  /**
   * Convert normalized value (0-1) to color using the cached colormap
   */
  public magnitudeToColor(normalizedValue: number): string {
    const m = clamp(normalizedValue, 0, 1);
    const map = this.activeColormap;
 
    if (!map || map.length === 0) {
      // Fallback if cache is somehow empty
      const v = Math.round(m * 255);
      return `rgba(${v}, ${v}, ${v}, 1)`;
    }
 
    // Find the closest color index in the cached map
    const index = Math.min(Math.floor(m * map.length), map.length - 1);
    const c = map[index];
 
    if (!c || c.length < 3) {
      // Fallback if color data is invalid
      const v = Math.round(m * 255);
      return `rgba(${v}, ${v}, ${v}, 1)`;
    }
 
    const r = c[0];
    const g = c[1];
    const b = c[2];
 
    // Convert to rgba string
    return `rgba(${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(b * 255)}, 1)`;
  }
 
  /**
   * Generate and cache the color array for the current scheme
   */
  private cacheColormap(): void {
    try {
      this.activeColormap = colormap({
        colormap: this.currentScheme,
        nshades: COLORMAP_SHADES,
        format: "float",
      });
      console.log(`ColorMapper: Cached colormap '${this.currentScheme}' with ${this.activeColormap.length} shades.`);
    } catch (error) {
      console.error(`Failed to generate colormap '${this.currentScheme}':`, error);
      // Fallback to a simple grayscale cache
      this.activeColormap = Array.from({ length: COLORMAP_SHADES }, (_, i) => {
        const v = i / (COLORMAP_SHADES - 1);
        return [v, v, v];
      });
      // Reset to a known good scheme
      this.currentScheme = COLOR_SCHEMES.VIRIDIS;
      this.cacheColormap(); // Re-cache viridis as fallback
    }
  }
}