@vitessce/neuroglancer 3.9.5 → 3.9.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,653 @@
1
+ // Utilities for constructing shaders that
2
+ // handle the coloring of Neuroglancer point annotation layers.
3
+ // References:
4
+ // - https://github.com/vitessce/vitessce/issues/2359#issuecomment-3572906947
5
+ // - https://chanzuckerberg.github.io/cryoet-data-portal/stable/neuroglancer_quickstart.html
6
+ import { PALETTE, getDefaultColor } from '@vitessce/utils';
7
+
8
+
9
+ /**
10
+ * Normalize an RGB color array from [0, 255] to [0, 1].
11
+ * @param {[number, number, number]} rgbColor
12
+ * @returns {[number, number, number]}
13
+ */
14
+ function normalizeColor(rgbColor) {
15
+ return rgbColor.map(c => c / 255);
16
+ }
17
+
18
+ /**
19
+ * Format a normalized color as a GLSL vec3 literal.
20
+ * @param {[number, number, number]} normalizedColor
21
+ * @returns {string}
22
+ */
23
+ function toVec3(normalizedColor) {
24
+ return `vec3(${normalizedColor.join(', ')})`;
25
+ }
26
+
27
+ /**
28
+ * Format a normalized color as a GLSL vec4 literal.
29
+ * @param {[number, number, number]} normalizedColor
30
+ * @param {number} alpha
31
+ * @returns {string}
32
+ */
33
+ function toVec4(normalizedColor, alpha) {
34
+ return `vec4(${normalizedColor.join(', ')}, ${alpha})`;
35
+ }
36
+
37
+ // ============================================================
38
+ // Case 1: spatialLayerColor
39
+ // ============================================================
40
+
41
+ /**
42
+ * Generate a shader for spatialLayerColor encoding with no feature selection.
43
+ * All points get the static color.
44
+ * @param {[number, number, number]} staticColor RGB (0-255).
45
+ * @param {number} opacity Opacity (0-1).
46
+ * @returns {string} A GLSL shader string.
47
+ */
48
+ export function getSpatialLayerColorShader(staticColor, opacity) {
49
+ const norm = normalizeColor(staticColor);
50
+ // lang: glsl
51
+ return `
52
+ void main() {
53
+ setColor(${toVec4(norm, opacity)});
54
+ }
55
+ `;
56
+ }
57
+
58
+
59
+ /**
60
+ * Generate a shader for spatialLayerColor encoding with feature selection.
61
+ * Selected features get the static color; unselected get the default color.
62
+ * @param {[number, number, number]} staticColor RGB (0-255).
63
+ * @param {number} opacity Opacity (0-1).
64
+ * @param {number[]} featureIndices Numeric indices of selected features.
65
+ * @param {[number, number, number]} defaultColor RGB (0-255) for unselected points.
66
+ * @param {string} featureIndexProp The property name for the feature index in the shader.
67
+ * @returns {string} A GLSL shader string.
68
+ */
69
+ export function getSpatialLayerColorWithSelectionShader(
70
+ staticColor, opacity, featureIndices, defaultColor, featureIndexProp,
71
+ ) {
72
+ const normStatic = normalizeColor(staticColor);
73
+ const normDefault = normalizeColor(defaultColor);
74
+ const numFeatures = featureIndices.length;
75
+ const indicesArr = `int selectedIndices[${numFeatures}] = int[${numFeatures}](${featureIndices.join(', ')});`;
76
+
77
+ // lang: glsl
78
+ return `
79
+ void main() {
80
+ int geneIndex = prop_${featureIndexProp}();
81
+ ${indicesArr}
82
+ bool isSelected = false;
83
+ for (int i = 0; i < ${numFeatures}; ++i) {
84
+ if (geneIndex == selectedIndices[i]) {
85
+ isSelected = true;
86
+ }
87
+ }
88
+ if (isSelected) {
89
+ setColor(${toVec4(normStatic, opacity)});
90
+ } else {
91
+ setColor(${toVec4(normDefault, opacity)});
92
+ }
93
+ }
94
+ `;
95
+ }
96
+
97
+
98
+ /**
99
+ * Generate a shader for spatialLayerColor encoding with feature selection
100
+ * and featureFilterMode='featureSelection' (hide unselected points).
101
+ * @param {[number, number, number]} staticColor RGB (0-255).
102
+ * @param {number} opacity Opacity (0-1).
103
+ * @param {number[]} featureIndices Numeric indices of selected features.
104
+ * @param {string} featureIndexProp The property name for the feature index in the shader.
105
+ * @returns {string} A GLSL shader string.
106
+ */
107
+ export function getSpatialLayerColorFilteredShader(
108
+ staticColor, opacity, featureIndices, featureIndexProp,
109
+ ) {
110
+ const normStatic = normalizeColor(staticColor);
111
+ const numFeatures = featureIndices.length;
112
+ const indicesArr = `int selectedIndices[${numFeatures}] = int[${numFeatures}](${featureIndices.join(', ')});`;
113
+
114
+ // lang: glsl
115
+ return `
116
+ void main() {
117
+ int geneIndex = prop_${featureIndexProp}();
118
+ ${indicesArr}
119
+ bool isSelected = false;
120
+ for (int i = 0; i < ${numFeatures}; ++i) {
121
+ if (geneIndex == selectedIndices[i]) {
122
+ isSelected = true;
123
+ }
124
+ }
125
+ if (!isSelected) {
126
+ discard;
127
+ }
128
+ setColor(${toVec4(normStatic, opacity)});
129
+ }
130
+ `;
131
+ }
132
+
133
+ // ============================================================
134
+ // Case 2: geneSelection
135
+ // ============================================================
136
+
137
+ /**
138
+ * Generate a shader for geneSelection encoding with no feature selection.
139
+ * All points get the static color (since no features are selected to
140
+ * determine per-feature colors).
141
+ * @param {[number, number, number]} staticColor RGB (0-255).
142
+ * @param {number} opacity Opacity (0-1).
143
+ * @returns {string} A GLSL shader string.
144
+ */
145
+ export function getGeneSelectionNoSelectionShader(staticColor, opacity) {
146
+ const norm = normalizeColor(staticColor);
147
+ // lang: glsl
148
+ return `
149
+ void main() {
150
+ setColor(${toVec4(norm, opacity)});
151
+ }
152
+ `;
153
+ }
154
+
155
+ /**
156
+ * Generate a shader for geneSelection encoding with feature selection.
157
+ * Each selected feature gets its color from featureColor; unselected
158
+ * points get the default color.
159
+ * @param {number[]} featureIndices Numeric indices of selected features.
160
+ * @param {[number, number, number][]} featureColors RGB (0-255) for each
161
+ * selected feature, in the same order as featureIndices.
162
+ * @param {[number, number, number]} staticColor Fallback RGB (0-255)
163
+ * for selected features without a specified color.
164
+ * @param {[number, number, number]} defaultColor RGB (0-255) for
165
+ * unselected points.
166
+ * @param {number} opacity Opacity (0-1).
167
+ * @param {string} featureIndexProp The property name for the feature index in the shader.
168
+ * @returns {string} A GLSL shader string.
169
+ */
170
+ export function getGeneSelectionWithSelectionShader(
171
+ featureIndices, featureColors, staticColor, defaultColor, opacity, featureIndexProp,
172
+ ) {
173
+ const numFeatures = featureIndices.length;
174
+ const normDefault = normalizeColor(defaultColor);
175
+ const normColors = featureColors.map(c => normalizeColor(c));
176
+ const normStatic = normalizeColor(staticColor);
177
+
178
+ const colorArr = normColors.map(
179
+ c => (c ? toVec3(c) : toVec3(normStatic)),
180
+ );
181
+
182
+ const indicesDecl = `int selectedIndices[${numFeatures}] = int[${numFeatures}](${featureIndices.join(', ')});`;
183
+ const colorsDecl = `vec3 featureColors[${numFeatures}] = vec3[${numFeatures}](${colorArr.join(', ')});`;
184
+ // lang: glsl
185
+ return `
186
+ void main() {
187
+ int geneIndex = prop_${featureIndexProp}();
188
+ ${indicesDecl}
189
+ ${colorsDecl}
190
+ vec4 color = ${toVec4(normDefault, opacity)};
191
+ for (int i = 0; i < ${numFeatures}; ++i) {
192
+ if (geneIndex == selectedIndices[i]) {
193
+ color = vec4(featureColors[i], ${opacity});
194
+ }
195
+ }
196
+ setColor(color);
197
+ }
198
+ `;
199
+ }
200
+
201
+ /**
202
+ * Generate a shader for geneSelection encoding with feature selection
203
+ * and featureFilterMode='featureSelection' (hide unselected points).
204
+ * @param {number[]} featureIndices Numeric indices of selected features.
205
+ * @param {[number, number, number][]} featureColors RGB (0-255) for each
206
+ * selected feature.
207
+ * @param {[number, number, number]} staticColor Fallback RGB (0-255).
208
+ * @param {number} opacity Opacity (0-1).
209
+ * @param {string} featureIndexProp The property name for the feature index in the shader.
210
+ * @returns {string} A GLSL shader string.
211
+ */
212
+ export function getGeneSelectionFilteredShader(
213
+ featureIndices, featureColors, staticColor, opacity, featureIndexProp,
214
+ ) {
215
+ const numFeatures = featureIndices.length;
216
+ const normColors = featureColors.map(c => normalizeColor(c));
217
+ const normStatic = normalizeColor(staticColor);
218
+
219
+ const colorArr = normColors.map(
220
+ c => (c ? toVec3(c) : toVec3(normStatic)),
221
+ );
222
+
223
+ const indicesDecl = `int selectedIndices[${numFeatures}] = int[${numFeatures}](${featureIndices.join(', ')});`;
224
+ const colorsDecl = `vec3 featureColors[${numFeatures}] = vec3[${numFeatures}](${colorArr.join(', ')});`;
225
+
226
+ // lang: glsl
227
+ return `
228
+ void main() {
229
+ int geneIndex = prop_${featureIndexProp}();
230
+ ${indicesDecl}
231
+ ${colorsDecl}
232
+ bool isSelected = false;
233
+ vec3 matchedColor = vec3(0.0);
234
+ for (int i = 0; i < ${numFeatures}; ++i) {
235
+ if (geneIndex == selectedIndices[i]) {
236
+ isSelected = true;
237
+ matchedColor = featureColors[i];
238
+ }
239
+ }
240
+ if (!isSelected) {
241
+ discard;
242
+ }
243
+ setColor(vec4(matchedColor, ${opacity}));
244
+ }
245
+ `;
246
+ }
247
+
248
+ // ============================================================
249
+ // Case 3: randomByFeature
250
+ // ============================================================
251
+
252
+ /**
253
+ * Generate a shader for randomByFeature encoding with no feature selection.
254
+ * Each feature gets a deterministic color from PALETTE based on its index.
255
+ * @param {number} opacity Opacity (0-1).
256
+ * @param {string} featureIndexProp The property name for the feature index in the shader.
257
+ * @returns {string} A GLSL shader string.
258
+ */
259
+ export function getRandomByFeatureShader(opacity, featureIndexProp) {
260
+ const paletteSize = PALETTE.length;
261
+ const normPalette = PALETTE.map(c => normalizeColor(c));
262
+ const paletteDecl = `vec3 palette[${paletteSize}] = vec3[${paletteSize}](${normPalette.map(c => toVec3(c)).join(', ')});`;
263
+
264
+ // lang: glsl
265
+ return `
266
+ void main() {
267
+ int geneIndex = prop_${featureIndexProp}();
268
+ ${paletteDecl}
269
+ int colorIdx = geneIndex - (geneIndex / ${paletteSize}) * ${paletteSize};
270
+ if (colorIdx < 0) { colorIdx = -colorIdx; }
271
+ vec3 color = palette[colorIdx];
272
+ setColor(vec4(color, ${opacity}));
273
+ }
274
+ `;
275
+ }
276
+
277
+ /**
278
+ * Generate a shader for randomByFeature encoding with feature selection.
279
+ * Selected features get their deterministic palette color; unselected
280
+ * points get the default color.
281
+ * @param {number[]} featureIndices Numeric indices of selected features.
282
+ * @param {[number, number, number]} defaultColor RGB (0-255) for unselected.
283
+ * @param {number} opacity Opacity (0-1).
284
+ * @param {string} featureIndexProp The property name for the feature index in the shader.
285
+ * @returns {string} A GLSL shader string.
286
+ */
287
+ export function getRandomByFeatureWithSelectionShader(
288
+ featureIndices, defaultColor, opacity, featureIndexProp,
289
+ ) {
290
+ const paletteSize = PALETTE.length;
291
+ const normPalette = PALETTE.map(c => normalizeColor(c));
292
+ const normDefault = normalizeColor(defaultColor);
293
+ const numFeatures = featureIndices.length;
294
+
295
+ const paletteDecl = `vec3 palette[${paletteSize}] = vec3[${paletteSize}](${normPalette.map(c => toVec3(c)).join(', ')});`;
296
+ const indicesDecl = `int selectedIndices[${numFeatures}] = int[${numFeatures}](${featureIndices.join(', ')});`;
297
+
298
+ // lang: glsl
299
+ return `
300
+ void main() {
301
+ int geneIndex = prop_${featureIndexProp}();
302
+ ${paletteDecl}
303
+ ${indicesDecl}
304
+ bool isSelected = false;
305
+ for (int i = 0; i < ${numFeatures}; ++i) {
306
+ if (geneIndex == selectedIndices[i]) {
307
+ isSelected = true;
308
+ }
309
+ }
310
+ if (isSelected) {
311
+ int colorIdx = geneIndex - (geneIndex / ${paletteSize}) * ${paletteSize};
312
+ if (colorIdx < 0) { colorIdx = -colorIdx; }
313
+ setColor(vec4(palette[colorIdx], ${opacity}));
314
+ } else {
315
+ setColor(${toVec4(normDefault, opacity)});
316
+ }
317
+ }
318
+ `;
319
+ }
320
+
321
+ /**
322
+ * Generate a shader for randomByFeature encoding with feature selection
323
+ * and featureFilterMode='featureSelection' (hide unselected points).
324
+ * @param {number[]} featureIndices Numeric indices of selected features.
325
+ * @param {number} opacity Opacity (0-1).
326
+ * @param {string} featureIndexProp The property name for the feature index in the shader.
327
+ * @returns {string} A GLSL shader string.
328
+ */
329
+ export function getRandomByFeatureFilteredShader(featureIndices, opacity, featureIndexProp) {
330
+ const paletteSize = PALETTE.length;
331
+ const normPalette = PALETTE.map(c => normalizeColor(c));
332
+ const numFeatures = featureIndices.length;
333
+
334
+ const paletteDecl = `vec3 palette[${paletteSize}] = vec3[${paletteSize}](${normPalette.map(c => toVec3(c)).join(', ')});`;
335
+ const indicesDecl = `int selectedIndices[${numFeatures}] = int[${numFeatures}](${featureIndices.join(', ')});`;
336
+
337
+ // lang: glsl
338
+ return `
339
+ void main() {
340
+ int geneIndex = prop_${featureIndexProp}();
341
+ ${paletteDecl}
342
+ ${indicesDecl}
343
+ bool isSelected = false;
344
+ for (int i = 0; i < ${numFeatures}; ++i) {
345
+ if (geneIndex == selectedIndices[i]) {
346
+ isSelected = true;
347
+ }
348
+ }
349
+ if (!isSelected) {
350
+ discard;
351
+ }
352
+ int colorIdx = geneIndex - (geneIndex / ${paletteSize}) * ${paletteSize};
353
+ if (colorIdx < 0) { colorIdx = -colorIdx; }
354
+ setColor(vec4(palette[colorIdx], ${opacity}));
355
+ }
356
+ `;
357
+ }
358
+
359
+ // ============================================================
360
+ // Case 4: random (per point)
361
+ // ============================================================
362
+
363
+ /**
364
+ * GLSL helper function that produces a pseudo-random float in [0, 1]
365
+ * from an integer value and a seed. Shared across random-per-point shaders.
366
+ * @returns {string} GLSL function source.
367
+ */
368
+ function hashToFloatGlsl() {
369
+ return `
370
+ float hashToFloat(int v, int seed) {
371
+ int h = v ^ (seed * 16777619);
372
+ h = h * 747796405 + 2891336453;
373
+ h = ((h >> 16) ^ h) * 2654435769;
374
+ h = ((h >> 16) ^ h);
375
+ return float(h & 0x7FFFFFFF) / float(0x7FFFFFFF);
376
+ }
377
+ `;
378
+ }
379
+
380
+ /**
381
+ * Generate a shader for random-per-point encoding with no feature selection.
382
+ * Each point gets a pseudo-random color based on its index.
383
+ * @param {number} opacity Opacity (0-1).
384
+ * @param {string} featureIndexProp The property name for the feature index in the shader.
385
+ * @param {string} pointIndexProp The property name for the point index in the shader.
386
+ * @returns {string} A GLSL shader string.
387
+ */
388
+ export function getRandomPerPointShader(opacity, featureIndexProp, pointIndexProp) {
389
+ // lang: glsl
390
+ return `
391
+ ${hashToFloatGlsl()}
392
+ void main() {
393
+ int geneIndex = prop_${featureIndexProp}();
394
+ int pointIndex = prop_${pointIndexProp}();
395
+ float r = hashToFloat(pointIndex, 0);
396
+ float g = hashToFloat(pointIndex, 1);
397
+ float b = hashToFloat(pointIndex, 2);
398
+ setColor(vec4(r, g, b, ${opacity}));
399
+ }
400
+ `;
401
+ }
402
+
403
+ /**
404
+ * Generate a shader for random-per-point encoding with feature selection.
405
+ * Selected points get a pseudo-random color; unselected get the default color.
406
+ * @param {number[]} featureIndices Numeric indices of selected features.
407
+ * @param {[number, number, number]} defaultColor RGB (0-255) for unselected.
408
+ * @param {number} opacity Opacity (0-1).
409
+ * @param {string} featureIndexProp The property name for the feature index in the shader.
410
+ * @param {string} pointIndexProp The property name for the point index in the shader.
411
+ * @returns {string} A GLSL shader string.
412
+ */
413
+ export function getRandomPerPointWithSelectionShader(
414
+ featureIndices, defaultColor, opacity, featureIndexProp, pointIndexProp,
415
+ ) {
416
+ const normDefault = normalizeColor(defaultColor);
417
+ const numFeatures = featureIndices.length;
418
+ const indicesDecl = `int selectedIndices[${numFeatures}] = int[${numFeatures}](${featureIndices.join(', ')});`;
419
+
420
+ // lang: glsl
421
+ return `
422
+ ${hashToFloatGlsl()}
423
+ void main() {
424
+ int geneIndex = prop_${featureIndexProp}();
425
+ int pointIndex = prop_${pointIndexProp}();
426
+ ${indicesDecl}
427
+ bool isSelected = false;
428
+ for (int i = 0; i < ${numFeatures}; ++i) {
429
+ if (geneIndex == selectedIndices[i]) {
430
+ isSelected = true;
431
+ }
432
+ }
433
+ if (isSelected) {
434
+ float r = hashToFloat(pointIndex, 0);
435
+ float g = hashToFloat(pointIndex, 1);
436
+ float b = hashToFloat(pointIndex, 2);
437
+ setColor(vec4(r, g, b, ${opacity}));
438
+ } else {
439
+ setColor(${toVec4(normDefault, opacity)});
440
+ }
441
+ }
442
+ `;
443
+ }
444
+
445
+ /**
446
+ * Generate a shader for random-per-point encoding with feature selection
447
+ * and featureFilterMode='featureSelection' (hide unselected points).
448
+ * @param {number[]} featureIndices Numeric indices of selected features.
449
+ * @param {number} opacity Opacity (0-1).
450
+ * @param {string} featureIndexProp The property name for the feature index in the shader.
451
+ * @param {string} pointIndexProp The property name for the point index in the shader.
452
+ * @returns {string} A GLSL shader string.
453
+ */
454
+ export function getRandomPerPointFilteredShader(
455
+ featureIndices, opacity, featureIndexProp, pointIndexProp,
456
+ ) {
457
+ const numFeatures = featureIndices.length;
458
+ const indicesDecl = `int selectedIndices[${numFeatures}] = int[${numFeatures}](${featureIndices.join(', ')});`;
459
+
460
+ // lang: glsl
461
+ return `
462
+ ${hashToFloatGlsl()}
463
+ void main() {
464
+ int geneIndex = prop_${featureIndexProp}();
465
+ int pointIndex = prop_${pointIndexProp}();
466
+ ${indicesDecl}
467
+ bool isSelected = false;
468
+ for (int i = 0; i < ${numFeatures}; ++i) {
469
+ if (geneIndex == selectedIndices[i]) {
470
+ isSelected = true;
471
+ }
472
+ }
473
+ if (!isSelected) {
474
+ discard;
475
+ }
476
+ float r = hashToFloat(pointIndex, 0);
477
+ float g = hashToFloat(pointIndex, 1);
478
+ float b = hashToFloat(pointIndex, 2);
479
+ setColor(vec4(r, g, b, ${opacity}));
480
+ }
481
+ `;
482
+ }
483
+
484
+
485
+ export function getPointsShader(layerCoordination) {
486
+ const {
487
+ theme,
488
+ featureIndex,
489
+ spatialLayerOpacity,
490
+ obsColorEncoding,
491
+ spatialLayerColor,
492
+ featureSelection,
493
+ featureFilterMode,
494
+ featureColor,
495
+
496
+ featureIndexProp,
497
+ pointIndexProp,
498
+ } = layerCoordination;
499
+
500
+ const defaultColor = getDefaultColor(theme);
501
+ const opacity = spatialLayerOpacity ?? 1.0;
502
+ const staticColor = (
503
+ Array.isArray(spatialLayerColor) && spatialLayerColor.length === 3
504
+ ? spatialLayerColor
505
+ : defaultColor
506
+ );
507
+
508
+ const hasFeatureSelection = (
509
+ Array.isArray(featureSelection) && featureSelection.length > 0
510
+ );
511
+ const isFiltered = featureFilterMode === 'featureSelection';
512
+
513
+ // Resolve selected feature names to numeric indices.
514
+ let featureIndices = [];
515
+ if (hasFeatureSelection && Array.isArray(featureIndex)) {
516
+ featureIndices = featureSelection
517
+ .map(name => featureIndex.indexOf(name))
518
+ .filter(i => i >= 0);
519
+ }
520
+ const hasResolvedIndices = featureIndices.length > 0;
521
+
522
+ // Resolve per-feature colors (in the same order as featureIndices).
523
+ const resolvedFeatureColors = hasResolvedIndices
524
+ ? featureSelection
525
+ .filter(name => featureIndex?.indexOf(name) >= 0)
526
+ .map((name) => {
527
+ const match = Array.isArray(featureColor)
528
+ ? featureColor.find(fc => fc.name === name)?.color
529
+ : null;
530
+ return match || staticColor;
531
+ })
532
+ : [];
533
+
534
+ // Points coloring cases:
535
+ // (See `createPointLayer` in spatial-beta/Spatial.js for more background.)
536
+
537
+ // Coloring cases:
538
+ // - spatialLayerColor: one color for all points.
539
+ // consider all as selected when featureSelection is null.
540
+ // - spatialLayerColor with featureSelection: one color for
541
+ // all selected points, default color for unselected points
542
+ // - spatialLayerColor with featureSelection and
543
+ // featureFilterMode 'featureSelection': one color for selected points,
544
+ // do not show unselected points
545
+
546
+ // - geneSelection: use colors from "featureColor".
547
+ // consider all as selected when featureSelection is null.
548
+ // - geneSelection with featureSelection: use colors from
549
+ // "featureColor" (array of { name, color: [r, g, b] }) for
550
+ // selected features, default color for unselected points
551
+ // - geneSelection with featureFilterMode 'featureSelection': use colors
552
+ // from "featureColor" for selected features,
553
+ // do not show unselected points
554
+
555
+ // - randomByFeature: random color for each feature
556
+ // (deterministic based on feature index).
557
+ // consider all as selected when featureSelection is null.
558
+ // - randomByFeature with preferFeatureColor: use colors from
559
+ // "featureColor" (array of { name, color: [r, g, b] }) where
560
+ // available, and random colors otherwise.
561
+ // - randomByFeature with featureSelection: random color for
562
+ // selected features, default color for unselected points
563
+ // - randomByFeature with featureSelection and
564
+ // featureFilterMode 'featureSelection': random color for
565
+ // selected features, do not show unselected points
566
+
567
+ // - random: random color for each point
568
+ // (deterministic based on point index).
569
+ // consider all as selected when featureSelection is null.
570
+ // - random with preferFeatureColor: use colors from "featureColor"
571
+ // (array of { name, color: [r, g, b] }) where available,
572
+ // and random colors otherwise.
573
+ // - random with featureSelection: random color for selected points,
574
+ // default color for unselected points
575
+ // - random with featureSelection and
576
+ // featureFilterMode 'featureSelection': random color for selected
577
+ // points, do not show unselected points
578
+
579
+
580
+ // ---- spatialLayerColor ----
581
+ if (obsColorEncoding === 'spatialLayerColor') {
582
+ if (!hasFeatureSelection || !hasResolvedIndices) {
583
+ return getSpatialLayerColorShader(staticColor, opacity);
584
+ }
585
+ if (isFiltered) {
586
+ return getSpatialLayerColorFilteredShader(
587
+ staticColor, opacity, featureIndices, featureIndexProp,
588
+ );
589
+ }
590
+ return getSpatialLayerColorWithSelectionShader(
591
+ staticColor, opacity, featureIndices, defaultColor, featureIndexProp,
592
+ );
593
+ }
594
+
595
+ // ---- geneSelection ----
596
+ if (obsColorEncoding === 'geneSelection') {
597
+ if (!featureIndexProp) {
598
+ throw new Error('In order to use gene-based color encoding for Neuroglancer Points, options.featureIndexProp must be specified for the obsPoints.ng-annotations fileType in the Vitessce configuration.');
599
+ }
600
+ if (!hasFeatureSelection || !hasResolvedIndices) {
601
+ return getGeneSelectionNoSelectionShader(staticColor, opacity);
602
+ }
603
+ if (isFiltered) {
604
+ return getGeneSelectionFilteredShader(
605
+ featureIndices, resolvedFeatureColors,
606
+ staticColor, opacity, featureIndexProp,
607
+ );
608
+ }
609
+ return getGeneSelectionWithSelectionShader(
610
+ featureIndices, resolvedFeatureColors,
611
+ staticColor, defaultColor, opacity, featureIndexProp,
612
+ );
613
+ }
614
+
615
+ // ---- randomByFeature ----
616
+ if (obsColorEncoding === 'randomByFeature') {
617
+ if (!featureIndexProp) {
618
+ throw new Error('In order to use gene-based color encoding for Neuroglancer Points, options.featureIndexProp must be specified for the obsPoints.ng-annotations fileType in the Vitessce configuration.');
619
+ }
620
+ if (!hasFeatureSelection || !hasResolvedIndices) {
621
+ return getRandomByFeatureShader(opacity, featureIndexProp);
622
+ }
623
+ if (isFiltered) {
624
+ return getRandomByFeatureFilteredShader(
625
+ featureIndices, opacity, featureIndexProp,
626
+ );
627
+ }
628
+ return getRandomByFeatureWithSelectionShader(
629
+ featureIndices, defaultColor, opacity, featureIndexProp,
630
+ );
631
+ }
632
+
633
+ // ---- random (per point) ----
634
+ if (obsColorEncoding === 'random') {
635
+ if (!pointIndexProp) {
636
+ throw new Error('In order to use per-point color encoding for Neuroglancer Points, options.pointIndexProp must be specified for the obsPoints.ng-annotations fileType in the Vitessce configuration.');
637
+ }
638
+ if (!hasFeatureSelection || !hasResolvedIndices) {
639
+ return getRandomPerPointShader(opacity, featureIndexProp, pointIndexProp);
640
+ }
641
+ if (isFiltered) {
642
+ return getRandomPerPointFilteredShader(
643
+ featureIndices, opacity, featureIndexProp, pointIndexProp,
644
+ );
645
+ }
646
+ return getRandomPerPointWithSelectionShader(
647
+ featureIndices, defaultColor, opacity, featureIndexProp, pointIndexProp,
648
+ );
649
+ }
650
+
651
+ // Fallback: static color.
652
+ return getSpatialLayerColorShader(staticColor, opacity);
653
+ }