File size: 2,195 Bytes
fc7d689
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
/**
 * Diverging colormap: coolwarm_r (reversed).
 * Negative values (compression) -> red/warm
 * Positive values (tension) -> blue/cool
 * Matches matplotlib coolwarm_r used in the desktop app.
 */

const STOPS = [
    [0.0, [0.706, 0.016, 0.150]],    // dark red (max compression)
    [0.25, [0.929, 0.567, 0.459]],
    [0.5, [0.865, 0.865, 0.865]],    // neutral gray (zero)
    [0.75, [0.557, 0.647, 0.925]],
    [1.0, [0.227, 0.298, 0.753]],    // dark blue (max tension)
];

function lerp(a, b, t) {
    return a + (b - a) * t;
}

export function colormapCoolwarmR(t) {
    t = Math.max(0, Math.min(1, t));
    for (let i = 0; i < STOPS.length - 1; i++) {
        const [t0, c0] = STOPS[i];
        const [t1, c1] = STOPS[i + 1];
        if (t >= t0 && t <= t1) {
            const f = (t - t0) / (t1 - t0);
            return [
                lerp(c0[0], c1[0], f),
                lerp(c0[1], c1[1], f),
                lerp(c0[2], c1[2], f),
            ];
        }
    }
    return STOPS[STOPS.length - 1][1];
}

/**
 * Map scalars to colors using diverging colormap centered at 0.
 * Returns Float32Array of n*3 RGB values.
 *
 * This matches PyVista's behavior: symmetric range around 0,
 * so pure compression maps to the red half [0 -> 0.5].
 */
export function mapScalarsToColors(values, vmin, vmax) {
    const n = values.length;
    const colors = new Float32Array(n * 3);
    const absMax = Math.max(Math.abs(vmin), Math.abs(vmax)) || 1e-6;

    for (let i = 0; i < n; i++) {
        // [-absMax, +absMax] -> [0, 1], with 0 -> 0.5
        const t = (values[i] / absMax + 1) * 0.5;
        const [r, g, b] = colormapCoolwarmR(t);
        colors[i * 3] = r;
        colors[i * 3 + 1] = g;
        colors[i * 3 + 2] = b;
    }
    return colors;
}

/**
 * Create CSS gradient for the colorbar.
 * Top = max (blue/tension), bottom = min (red/compression).
 */
export function colormapGradientCSS() {
    const stops = [];
    for (let i = 0; i <= 20; i++) {
        const t = 1 - i / 20;
        const [r, g, b] = colormapCoolwarmR(t);
        stops.push(`rgb(${r * 255 | 0},${g * 255 | 0},${b * 255 | 0})`);
    }
    return `linear-gradient(to bottom, ${stops.join(', ')})`;
}