@vitessce/neuroglancer 3.9.7 → 3.9.8

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.
@@ -67,6 +67,10 @@ let viewerNoKey;
67
67
  * @property {() => void} onSelectionDetailsStateChanged
68
68
  * A function of the form `() => {}` to respond to selection changes in the viewer.
69
69
  * @property {() => void} onViewerStateChanged
70
+ * @property {(isLoaded: boolean) => void} onLayerLoadingChange
71
+ * A function of the form `(isLoaded) => {}`, called when layer loading state changes.
72
+ * The `isLoaded` argument will be `true` when all segmentation layers have finished loading
73
+ * their data sources, or `false` when layers are still loading.
70
74
  *
71
75
  * @property {Array<Object>} callbacks
72
76
  * // ngServer: string,
@@ -411,6 +415,7 @@ export default class Neuroglancer extends React.Component {
411
415
  onVisibleChanged: null,
412
416
  onSelectionDetailsStateChanged: null,
413
417
  onViewerStateChanged: null,
418
+ onLayerLoadingChange: null,
414
419
  key: null,
415
420
  callbacks: [],
416
421
  ngServer: 'https://neuroglancer-demo.appspot.com/',
@@ -488,7 +493,7 @@ export default class Neuroglancer extends React.Component {
488
493
  // "obsSegmentations-init_A_obsSegmentations_0-init_A_obsSegmentations_0"
489
494
  const layerScope = Object.keys(cellColorMappingByLayer).find(scope => layer.name?.includes(scope));
490
495
 
491
- const selected = { ...(cellColorMappingByLayer[layerScope] || {}) };
496
+ const selected = { ...(cellColorMappingByLayer[layerScope]?.colors || {}) };
492
497
 
493
498
  // Track all known IDs for this layer scope
494
499
  if (!this.allKnownIdsByLayer) this.allKnownIdsByLayer = {};
@@ -625,7 +630,41 @@ export default class Neuroglancer extends React.Component {
625
630
  viewerNoKey = this.viewer;
626
631
  }
627
632
 
628
- // TODO: This is purely for debugging and we need to remove it.
633
+ const { visibleChunksChanged } = this.viewer.chunkQueueManager;
634
+ let firstChunkLoaded = false;
635
+ this.disposers.push(visibleChunksChanged.add(() => {
636
+ if (!firstChunkLoaded) {
637
+ for (const layer of this.viewer.layerManager.managedLayers) {
638
+ if (layer.layer instanceof SegmentationUserLayer) {
639
+ const hasVisibleChunk = layer.layer.renderLayers?.some((rl) => {
640
+ const {
641
+ numVisibleChunksAvailable,
642
+ numVisibleChunksNeeded,
643
+ } = rl.layerChunkProgressInfo || {};
644
+ if (!numVisibleChunksNeeded || !numVisibleChunksAvailable) return false;
645
+ // Neuroglancer only shows chunks when a certain % is loaded.
646
+ // The 0.25 is from testing different values, can be reduced to 0.2 to shorten loader time
647
+ return (numVisibleChunksAvailable / numVisibleChunksNeeded) > 0.25;
648
+ });
649
+ if (hasVisibleChunk) {
650
+ firstChunkLoaded = true;
651
+ // Two frames to avoid flash while the following two happens
652
+ // Neuroglancer issues WebGL draw calls
653
+ requestAnimationFrame(() => {
654
+ // GPU has painted, pixels visible on screen
655
+ requestAnimationFrame(() => {
656
+ this.props.onLayerLoadingChange?.(true);
657
+ });
658
+ });
659
+ return;
660
+ }
661
+ }
662
+ }
663
+ }
664
+ }));
665
+ this.disposers.push(() => { firstChunkLoaded = false; });
666
+
667
+ // TODO: This is purely for debugging - exposes the NG viewer to be tested via console
629
668
  // window.viewer = this.viewer;
630
669
  }
631
670
 
@@ -667,6 +706,27 @@ export default class Neuroglancer extends React.Component {
667
706
  if (layer.layer instanceof SegmentationUserLayer) {
668
707
  const { segmentSelectionState } = layer.layer.displayState;
669
708
  segmentSelectionState.set(selectedSegments[layer.name]);
709
+ const layerScope = Object.keys(cellColorMappingByLayer).find(
710
+ scope => layer.name?.includes(scope),
711
+ );
712
+ if (layerScope) {
713
+ const opacity = cellColorMappingByLayer[layerScope]?.opacity ?? 1.0;
714
+ layer.layer.displayState.objectAlpha.value = opacity;
715
+ }
716
+ }
717
+ // Update annotation layer shaders from viewerState config,
718
+ // skipping update if shader is unchanged to avoid costly re-renders
719
+ if (layer.layer instanceof AnnotationUserLayer) {
720
+ const matchingLayer = (viewerState?.layers || []).find(
721
+ l => l.name === layer.name,
722
+ );
723
+ if (matchingLayer?.shader) {
724
+ /* eslint-disable-next-line no-underscore-dangle */
725
+ const currentShader = layer.layer.annotationDisplayState.shader.value_;
726
+ if (currentShader !== matchingLayer.shader) {
727
+ layer.layer.annotationDisplayState.shader.value = matchingLayer.shader;
728
+ }
729
+ }
670
730
  }
671
731
  }
672
732
 
@@ -717,9 +777,11 @@ export default class Neuroglancer extends React.Component {
717
777
  // If colors changed (but layers didn’t): re-apply colors
718
778
  // this was to avid NG randomly assigning colors to the segments by resetting them
719
779
  const prevSize = prevProps.cellColorMapping
720
- ? Object.keys(prevProps.cellColorMapping).length : 0;
780
+ ? Object.values(prevProps.cellColorMapping)
781
+ .reduce((acc, v) => acc + Object.keys(v?.colors || {}).length, 0) : 0;
721
782
  const currSize = cellColorMappingByLayer
722
- ? Object.keys(cellColorMappingByLayer).length : 0;
783
+ ? Object.values(cellColorMappingByLayer)
784
+ .reduce((acc, v) => acc + Object.keys(v?.colors || {}).length, 0) : 0;
723
785
  const mappingRefChanged = prevProps.cellColorMapping !== this.props.cellColorMapping;
724
786
  if (!this.didLayersChange(prevVS, viewerState)
725
787
  && (mappingRefChanged || prevSize !== currSize)) {
@@ -732,7 +794,7 @@ export default class Neuroglancer extends React.Component {
732
794
  // We only restore layers (not pose) when sources change OR on the first time segments appear.
733
795
  const stripSegFields = layers => (layers || []).map((l) => {
734
796
  if (!l) return l;
735
- const { segments, segmentColors, ...rest } = l;
797
+ const { segments, segmentColors, objectAlpha, ...rest } = l;
736
798
  return rest; // ignore segments + segmentColors for comparison
737
799
  });
738
800
 
@@ -106,6 +106,7 @@ export function toNgLayerName(dataType, layerScope, channelScope = null) {
106
106
  */
107
107
  export function useNeuroglancerViewerState(
108
108
  theme,
109
+ showAxisLines,
109
110
  segmentationLayerScopes,
110
111
  segmentationChannelScopesByLayer,
111
112
  segmentationLayerCoordination,
@@ -140,20 +141,35 @@ export function useNeuroglancerViewerState(
140
141
  const {
141
142
  spatialChannelVisible,
142
143
  } = channelCoordination || {};
144
+ const { source: ngSource, ...otherNgOptions } = layerData.neuroglancerOptions ?? {};
145
+
146
+ // Build source: if neuroglancerOptions has subsources
147
+ const hasNgSourceOptions = layerData.neuroglancerOptions?.subsources
148
+ || layerData.neuroglancerOptions?.enableDefaultSubsources !== undefined;
149
+
150
+ const source = hasNgSourceOptions
151
+ ? {
152
+ url: toPrecomputedSource(layerUrl),
153
+ subsources: layerData.neuroglancerOptions.subsources,
154
+ enableDefaultSubsources: layerData.neuroglancerOptions.enableDefaultSubsources
155
+ ?? false,
156
+ }
157
+ : toPrecomputedSource(layerUrl);
158
+
143
159
  result = {
144
160
  ...result,
145
161
  layers: [
146
162
  ...result.layers,
147
163
  {
148
164
  type: 'segmentation',
149
- source: toPrecomputedSource(layerUrl),
165
+ source,
150
166
  segments: [],
151
167
  name: toNgLayerName(DataType.OBS_SEGMENTATIONS, layerScope, channelScope),
152
168
  visible: spatialLayerVisible && spatialChannelVisible, // Both layer and channel
153
169
  // visibility must be true for the layer to be visible.
154
170
  // TODO: update this to extract specific properties from
155
171
  // neuroglancerOptions as needed.
156
- ...(layerData.neuroglancerOptions ?? {}),
172
+ ...otherNgOptions,
157
173
  },
158
174
  ],
159
175
  };
@@ -180,6 +196,7 @@ export function useNeuroglancerViewerState(
180
196
  featureSelection,
181
197
  featureFilterMode,
182
198
  featureColor,
199
+ spatialPointStrokeWidth,
183
200
  } = layerCoordination || {};
184
201
 
185
202
  // Dynamically construct the shader based on the color encoding
@@ -196,6 +213,7 @@ export function useNeuroglancerViewerState(
196
213
 
197
214
  featureIndexProp: layerData.neuroglancerOptions?.featureIndexProp,
198
215
  pointIndexProp: layerData.neuroglancerOptions?.pointIndexProp,
216
+ pointMarkerBorderWidth: spatialPointStrokeWidth ?? 0.0,
199
217
  });
200
218
 
201
219
  result = {
@@ -235,6 +253,7 @@ export function useNeuroglancerViewerState(
235
253
  return result;
236
254
  }, {
237
255
  theme,
256
+ showAxisLines,
238
257
  segmentationLayerScopes,
239
258
  segmentationChannelScopesByLayer,
240
259
  segmentationLayerCoordination,
@@ -15,6 +15,18 @@ function normalizeColor(rgbColor) {
15
15
  return rgbColor.map(c => c / 255);
16
16
  }
17
17
 
18
+ /**
19
+ * GLSL call to set point marker border width.
20
+ * Set to 0.0 to remove the outline.
21
+ * @param {number} borderWidth
22
+ * @returns {string}
23
+ */
24
+ function borderWidthGlsl(borderWidth = 0.0) {
25
+ // must be decimal/float value
26
+ return `setPointMarkerBorderWidth(${borderWidth.toFixed(1)});`;
27
+ }
28
+
29
+
18
30
  /**
19
31
  * Format a normalized color as a GLSL vec3 literal.
20
32
  * @param {[number, number, number]} normalizedColor
@@ -45,12 +57,13 @@ function toVec4(normalizedColor, alpha) {
45
57
  * @param {number} opacity Opacity (0-1).
46
58
  * @returns {string} A GLSL shader string.
47
59
  */
48
- export function getSpatialLayerColorShader(staticColor, opacity) {
60
+ export function getSpatialLayerColorShader(staticColor, opacity, borderWidth = 0.0) {
49
61
  const norm = normalizeColor(staticColor);
50
62
  // lang: glsl
51
63
  return `
52
64
  void main() {
53
65
  setColor(${toVec4(norm, opacity)});
66
+ ${borderWidthGlsl(borderWidth)}
54
67
  }
55
68
  `;
56
69
  }
@@ -67,7 +80,7 @@ export function getSpatialLayerColorShader(staticColor, opacity) {
67
80
  * @returns {string} A GLSL shader string.
68
81
  */
69
82
  export function getSpatialLayerColorWithSelectionShader(
70
- staticColor, opacity, featureIndices, defaultColor, featureIndexProp,
83
+ staticColor, opacity, featureIndices, defaultColor, featureIndexProp, borderWidth = 0.0,
71
84
  ) {
72
85
  const normStatic = normalizeColor(staticColor);
73
86
  const normDefault = normalizeColor(defaultColor);
@@ -87,8 +100,10 @@ export function getSpatialLayerColorWithSelectionShader(
87
100
  }
88
101
  if (isSelected) {
89
102
  setColor(${toVec4(normStatic, opacity)});
103
+ ${borderWidthGlsl(borderWidth)}
90
104
  } else {
91
105
  setColor(${toVec4(normDefault, opacity)});
106
+ ${borderWidthGlsl(borderWidth)}
92
107
  }
93
108
  }
94
109
  `;
@@ -105,7 +120,7 @@ export function getSpatialLayerColorWithSelectionShader(
105
120
  * @returns {string} A GLSL shader string.
106
121
  */
107
122
  export function getSpatialLayerColorFilteredShader(
108
- staticColor, opacity, featureIndices, featureIndexProp,
123
+ staticColor, opacity, featureIndices, featureIndexProp, borderWidth = 0.0,
109
124
  ) {
110
125
  const normStatic = normalizeColor(staticColor);
111
126
  const numFeatures = featureIndices.length;
@@ -126,6 +141,7 @@ export function getSpatialLayerColorFilteredShader(
126
141
  discard;
127
142
  }
128
143
  setColor(${toVec4(normStatic, opacity)});
144
+ ${borderWidthGlsl(borderWidth)}
129
145
  }
130
146
  `;
131
147
  }
@@ -142,12 +158,13 @@ export function getSpatialLayerColorFilteredShader(
142
158
  * @param {number} opacity Opacity (0-1).
143
159
  * @returns {string} A GLSL shader string.
144
160
  */
145
- export function getGeneSelectionNoSelectionShader(staticColor, opacity) {
161
+ export function getGeneSelectionNoSelectionShader(staticColor, opacity, borderWidth = 0.0) {
146
162
  const norm = normalizeColor(staticColor);
147
163
  // lang: glsl
148
164
  return `
149
165
  void main() {
150
166
  setColor(${toVec4(norm, opacity)});
167
+ ${borderWidthGlsl(borderWidth)}
151
168
  }
152
169
  `;
153
170
  }
@@ -168,7 +185,13 @@ export function getGeneSelectionNoSelectionShader(staticColor, opacity) {
168
185
  * @returns {string} A GLSL shader string.
169
186
  */
170
187
  export function getGeneSelectionWithSelectionShader(
171
- featureIndices, featureColors, staticColor, defaultColor, opacity, featureIndexProp,
188
+ featureIndices,
189
+ featureColors,
190
+ staticColor,
191
+ defaultColor,
192
+ opacity,
193
+ featureIndexProp,
194
+ borderWidth = 0.0,
172
195
  ) {
173
196
  const numFeatures = featureIndices.length;
174
197
  const normDefault = normalizeColor(defaultColor);
@@ -194,6 +217,7 @@ export function getGeneSelectionWithSelectionShader(
194
217
  }
195
218
  }
196
219
  setColor(color);
220
+ ${borderWidthGlsl(borderWidth)}
197
221
  }
198
222
  `;
199
223
  }
@@ -210,7 +234,7 @@ export function getGeneSelectionWithSelectionShader(
210
234
  * @returns {string} A GLSL shader string.
211
235
  */
212
236
  export function getGeneSelectionFilteredShader(
213
- featureIndices, featureColors, staticColor, opacity, featureIndexProp,
237
+ featureIndices, featureColors, staticColor, opacity, featureIndexProp, borderWidth = 0.0,
214
238
  ) {
215
239
  const numFeatures = featureIndices.length;
216
240
  const normColors = featureColors.map(c => normalizeColor(c));
@@ -241,6 +265,7 @@ export function getGeneSelectionFilteredShader(
241
265
  discard;
242
266
  }
243
267
  setColor(vec4(matchedColor, ${opacity}));
268
+ ${borderWidthGlsl(borderWidth)}
244
269
  }
245
270
  `;
246
271
  }
@@ -256,7 +281,7 @@ export function getGeneSelectionFilteredShader(
256
281
  * @param {string} featureIndexProp The property name for the feature index in the shader.
257
282
  * @returns {string} A GLSL shader string.
258
283
  */
259
- export function getRandomByFeatureShader(opacity, featureIndexProp) {
284
+ export function getRandomByFeatureShader(opacity, featureIndexProp, borderWidth = 0.0) {
260
285
  const paletteSize = PALETTE.length;
261
286
  const normPalette = PALETTE.map(c => normalizeColor(c));
262
287
  const paletteDecl = `vec3 palette[${paletteSize}] = vec3[${paletteSize}](${normPalette.map(c => toVec3(c)).join(', ')});`;
@@ -270,6 +295,7 @@ export function getRandomByFeatureShader(opacity, featureIndexProp) {
270
295
  if (colorIdx < 0) { colorIdx = -colorIdx; }
271
296
  vec3 color = palette[colorIdx];
272
297
  setColor(vec4(color, ${opacity}));
298
+ ${borderWidthGlsl(borderWidth)}
273
299
  }
274
300
  `;
275
301
  }
@@ -285,7 +311,7 @@ export function getRandomByFeatureShader(opacity, featureIndexProp) {
285
311
  * @returns {string} A GLSL shader string.
286
312
  */
287
313
  export function getRandomByFeatureWithSelectionShader(
288
- featureIndices, defaultColor, opacity, featureIndexProp,
314
+ featureIndices, defaultColor, opacity, featureIndexProp, borderWidth = 0.0,
289
315
  ) {
290
316
  const paletteSize = PALETTE.length;
291
317
  const normPalette = PALETTE.map(c => normalizeColor(c));
@@ -311,8 +337,10 @@ export function getRandomByFeatureWithSelectionShader(
311
337
  int colorIdx = geneIndex - (geneIndex / ${paletteSize}) * ${paletteSize};
312
338
  if (colorIdx < 0) { colorIdx = -colorIdx; }
313
339
  setColor(vec4(palette[colorIdx], ${opacity}));
340
+ ${borderWidthGlsl(borderWidth)}
314
341
  } else {
315
342
  setColor(${toVec4(normDefault, opacity)});
343
+ ${borderWidthGlsl(borderWidth)}
316
344
  }
317
345
  }
318
346
  `;
@@ -326,7 +354,12 @@ export function getRandomByFeatureWithSelectionShader(
326
354
  * @param {string} featureIndexProp The property name for the feature index in the shader.
327
355
  * @returns {string} A GLSL shader string.
328
356
  */
329
- export function getRandomByFeatureFilteredShader(featureIndices, opacity, featureIndexProp) {
357
+ export function getRandomByFeatureFilteredShader(
358
+ featureIndices,
359
+ opacity,
360
+ featureIndexProp,
361
+ borderWidth = 0.0,
362
+ ) {
330
363
  const paletteSize = PALETTE.length;
331
364
  const normPalette = PALETTE.map(c => normalizeColor(c));
332
365
  const numFeatures = featureIndices.length;
@@ -352,6 +385,7 @@ export function getRandomByFeatureFilteredShader(featureIndices, opacity, featur
352
385
  int colorIdx = geneIndex - (geneIndex / ${paletteSize}) * ${paletteSize};
353
386
  if (colorIdx < 0) { colorIdx = -colorIdx; }
354
387
  setColor(vec4(palette[colorIdx], ${opacity}));
388
+ ${borderWidthGlsl(borderWidth)}
355
389
  }
356
390
  `;
357
391
  }
@@ -385,7 +419,12 @@ function hashToFloatGlsl() {
385
419
  * @param {string} pointIndexProp The property name for the point index in the shader.
386
420
  * @returns {string} A GLSL shader string.
387
421
  */
388
- export function getRandomPerPointShader(opacity, featureIndexProp, pointIndexProp) {
422
+ export function getRandomPerPointShader(
423
+ opacity,
424
+ featureIndexProp,
425
+ pointIndexProp,
426
+ borderWidth = 0.0,
427
+ ) {
389
428
  // lang: glsl
390
429
  return `
391
430
  ${hashToFloatGlsl()}
@@ -396,6 +435,7 @@ export function getRandomPerPointShader(opacity, featureIndexProp, pointIndexPro
396
435
  float g = hashToFloat(pointIndex, 1);
397
436
  float b = hashToFloat(pointIndex, 2);
398
437
  setColor(vec4(r, g, b, ${opacity}));
438
+ ${borderWidthGlsl(borderWidth)}
399
439
  }
400
440
  `;
401
441
  }
@@ -411,7 +451,7 @@ export function getRandomPerPointShader(opacity, featureIndexProp, pointIndexPro
411
451
  * @returns {string} A GLSL shader string.
412
452
  */
413
453
  export function getRandomPerPointWithSelectionShader(
414
- featureIndices, defaultColor, opacity, featureIndexProp, pointIndexProp,
454
+ featureIndices, defaultColor, opacity, featureIndexProp, pointIndexProp, borderWidth = 0.0,
415
455
  ) {
416
456
  const normDefault = normalizeColor(defaultColor);
417
457
  const numFeatures = featureIndices.length;
@@ -435,8 +475,10 @@ export function getRandomPerPointWithSelectionShader(
435
475
  float g = hashToFloat(pointIndex, 1);
436
476
  float b = hashToFloat(pointIndex, 2);
437
477
  setColor(vec4(r, g, b, ${opacity}));
478
+ ${borderWidthGlsl(borderWidth)}
438
479
  } else {
439
480
  setColor(${toVec4(normDefault, opacity)});
481
+ ${borderWidthGlsl(borderWidth)}
440
482
  }
441
483
  }
442
484
  `;
@@ -452,7 +494,7 @@ export function getRandomPerPointWithSelectionShader(
452
494
  * @returns {string} A GLSL shader string.
453
495
  */
454
496
  export function getRandomPerPointFilteredShader(
455
- featureIndices, opacity, featureIndexProp, pointIndexProp,
497
+ featureIndices, opacity, featureIndexProp, pointIndexProp, borderWidth = 0.0,
456
498
  ) {
457
499
  const numFeatures = featureIndices.length;
458
500
  const indicesDecl = `int selectedIndices[${numFeatures}] = int[${numFeatures}](${featureIndices.join(', ')});`;
@@ -477,6 +519,7 @@ export function getRandomPerPointFilteredShader(
477
519
  float g = hashToFloat(pointIndex, 1);
478
520
  float b = hashToFloat(pointIndex, 2);
479
521
  setColor(vec4(r, g, b, ${opacity}));
522
+ ${borderWidthGlsl(borderWidth)}
480
523
  }
481
524
  `;
482
525
  }
@@ -492,7 +535,7 @@ export function getPointsShader(layerCoordination) {
492
535
  featureSelection,
493
536
  featureFilterMode,
494
537
  featureColor,
495
-
538
+ pointMarkerBorderWidth = 0.0,
496
539
  featureIndexProp,
497
540
  pointIndexProp,
498
541
  } = layerCoordination;
@@ -580,15 +623,15 @@ export function getPointsShader(layerCoordination) {
580
623
  // ---- spatialLayerColor ----
581
624
  if (obsColorEncoding === 'spatialLayerColor') {
582
625
  if (!hasFeatureSelection || !hasResolvedIndices) {
583
- return getSpatialLayerColorShader(staticColor, opacity);
626
+ return getSpatialLayerColorShader(staticColor, opacity, pointMarkerBorderWidth);
584
627
  }
585
628
  if (isFiltered) {
586
629
  return getSpatialLayerColorFilteredShader(
587
- staticColor, opacity, featureIndices, featureIndexProp,
630
+ staticColor, opacity, featureIndices, featureIndexProp, pointMarkerBorderWidth,
588
631
  );
589
632
  }
590
633
  return getSpatialLayerColorWithSelectionShader(
591
- staticColor, opacity, featureIndices, defaultColor, featureIndexProp,
634
+ staticColor, opacity, featureIndices, defaultColor, featureIndexProp, pointMarkerBorderWidth,
592
635
  );
593
636
  }
594
637
 
@@ -598,17 +641,17 @@ export function getPointsShader(layerCoordination) {
598
641
  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
642
  }
600
643
  if (!hasFeatureSelection || !hasResolvedIndices) {
601
- return getGeneSelectionNoSelectionShader(staticColor, opacity);
644
+ return getGeneSelectionNoSelectionShader(staticColor, opacity, pointMarkerBorderWidth);
602
645
  }
603
646
  if (isFiltered) {
604
647
  return getGeneSelectionFilteredShader(
605
648
  featureIndices, resolvedFeatureColors,
606
- staticColor, opacity, featureIndexProp,
649
+ staticColor, opacity, featureIndexProp, pointMarkerBorderWidth,
607
650
  );
608
651
  }
609
652
  return getGeneSelectionWithSelectionShader(
610
653
  featureIndices, resolvedFeatureColors,
611
- staticColor, defaultColor, opacity, featureIndexProp,
654
+ staticColor, defaultColor, opacity, featureIndexProp, pointMarkerBorderWidth,
612
655
  );
613
656
  }
614
657
 
@@ -618,15 +661,15 @@ export function getPointsShader(layerCoordination) {
618
661
  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
662
  }
620
663
  if (!hasFeatureSelection || !hasResolvedIndices) {
621
- return getRandomByFeatureShader(opacity, featureIndexProp);
664
+ return getRandomByFeatureShader(opacity, featureIndexProp, pointMarkerBorderWidth);
622
665
  }
623
666
  if (isFiltered) {
624
667
  return getRandomByFeatureFilteredShader(
625
- featureIndices, opacity, featureIndexProp,
668
+ featureIndices, opacity, featureIndexProp, pointMarkerBorderWidth,
626
669
  );
627
670
  }
628
671
  return getRandomByFeatureWithSelectionShader(
629
- featureIndices, defaultColor, opacity, featureIndexProp,
672
+ featureIndices, defaultColor, opacity, featureIndexProp, pointMarkerBorderWidth,
630
673
  );
631
674
  }
632
675
 
@@ -636,18 +679,28 @@ export function getPointsShader(layerCoordination) {
636
679
  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
680
  }
638
681
  if (!hasFeatureSelection || !hasResolvedIndices) {
639
- return getRandomPerPointShader(opacity, featureIndexProp, pointIndexProp);
682
+ return getRandomPerPointShader(
683
+ opacity,
684
+ featureIndexProp,
685
+ pointIndexProp,
686
+ pointMarkerBorderWidth,
687
+ );
640
688
  }
641
689
  if (isFiltered) {
642
690
  return getRandomPerPointFilteredShader(
643
- featureIndices, opacity, featureIndexProp, pointIndexProp,
691
+ featureIndices, opacity, featureIndexProp, pointIndexProp, pointMarkerBorderWidth,
644
692
  );
645
693
  }
646
694
  return getRandomPerPointWithSelectionShader(
647
- featureIndices, defaultColor, opacity, featureIndexProp, pointIndexProp,
695
+ featureIndices,
696
+ defaultColor,
697
+ opacity,
698
+ featureIndexProp,
699
+ pointIndexProp,
700
+ pointMarkerBorderWidth,
648
701
  );
649
702
  }
650
703
 
651
704
  // Fallback: static color.
652
- return getSpatialLayerColorShader(staticColor, opacity);
705
+ return getSpatialLayerColorShader(staticColor, opacity, pointMarkerBorderWidth);
653
706
  }
@@ -47,6 +47,7 @@ describe('getSpatialLayerColorShader', () => {
47
47
  const expected = `
48
48
  void main() {
49
49
  setColor(vec4(1, 0.5019607843137255, 0, 0.8));
50
+ setPointMarkerBorderWidth(0.0);
50
51
  }
51
52
  `;
52
53
  expectShaderEqual(result, expected);
@@ -57,6 +58,7 @@ void main() {
57
58
  const expected = `
58
59
  void main() {
59
60
  setColor(vec4(0, 0, 0, 0));
61
+ setPointMarkerBorderWidth(0.0);
60
62
  }
61
63
  `;
62
64
  expectShaderEqual(result, expected);
@@ -67,6 +69,7 @@ void main() {
67
69
  const expected = `
68
70
  void main() {
69
71
  setColor(vec4(1, 1, 1, 1));
72
+ setPointMarkerBorderWidth(0.0);
70
73
  }
71
74
  `;
72
75
  expectShaderEqual(result, expected);
@@ -90,8 +93,10 @@ void main() {
90
93
  }
91
94
  if (isSelected) {
92
95
  setColor(vec4(1, 0, 0, 0.5));
96
+ setPointMarkerBorderWidth(0.0);
93
97
  } else {
94
98
  setColor(vec4(0.5019607843137255, 0.5019607843137255, 0.5019607843137255, 0.5));
99
+ setPointMarkerBorderWidth(0.0);
95
100
  }
96
101
  }
97
102
  `;
@@ -114,8 +119,10 @@ void main() {
114
119
  }
115
120
  if (isSelected) {
116
121
  setColor(vec4(0, 1, 0, 1));
122
+ setPointMarkerBorderWidth(0.0);
117
123
  } else {
118
124
  setColor(vec4(0, 0, 0, 1));
125
+ setPointMarkerBorderWidth(0.0);
119
126
  }
120
127
  }
121
128
  `;
@@ -142,6 +149,7 @@ void main() {
142
149
  discard;
143
150
  }
144
151
  setColor(vec4(0, 0, 1, 0.9));
152
+ setPointMarkerBorderWidth(0.0);
145
153
  }
146
154
  `;
147
155
  expectShaderEqual(result, expected);
@@ -158,6 +166,7 @@ describe('getGeneSelectionNoSelectionShader', () => {
158
166
  const expected = `
159
167
  void main() {
160
168
  setColor(vec4(0.39215686274509803, 0.7843137254901961, 0.19607843137254902, 0.7));
169
+ setPointMarkerBorderWidth(0.0);
161
170
  }
162
171
  `;
163
172
  expectShaderEqual(result, expected);
@@ -186,6 +195,7 @@ void main() {
186
195
  }
187
196
  }
188
197
  setColor(color);
198
+ setPointMarkerBorderWidth(0.0);
189
199
  }
190
200
  `;
191
201
  expectShaderEqual(result, expected);
@@ -216,6 +226,7 @@ void main() {
216
226
  }
217
227
  }
218
228
  setColor(color);
229
+ setPointMarkerBorderWidth(0.0);
219
230
  }
220
231
  `;
221
232
  expectShaderEqual(result, expected);
@@ -248,6 +259,7 @@ void main() {
248
259
  discard;
249
260
  }
250
261
  setColor(vec4(matchedColor, 0.75));
262
+ setPointMarkerBorderWidth(0.0);
251
263
  }
252
264
  `;
253
265
  expectShaderEqual(result, expected);
@@ -269,6 +281,7 @@ void main() {
269
281
  if (colorIdx < 0) { colorIdx = -colorIdx; }
270
282
  vec3 color = palette[colorIdx];
271
283
  setColor(vec4(color, 0.5));
284
+ setPointMarkerBorderWidth(0.0);
272
285
  }
273
286
  `;
274
287
  expectShaderEqual(result, expected);
@@ -295,8 +308,10 @@ void main() {
295
308
  int colorIdx = geneIndex - (geneIndex / 3) * 3;
296
309
  if (colorIdx < 0) { colorIdx = -colorIdx; }
297
310
  setColor(vec4(palette[colorIdx], 0.8));
311
+ setPointMarkerBorderWidth(0.0);
298
312
  } else {
299
313
  setColor(vec4(0.19607843137254902, 0.19607843137254902, 0.19607843137254902, 0.8));
314
+ setPointMarkerBorderWidth(0.0);
300
315
  }
301
316
  }
302
317
  `;
@@ -324,6 +339,7 @@ void main() {
324
339
  int colorIdx = geneIndex - (geneIndex / 3) * 3;
325
340
  if (colorIdx < 0) { colorIdx = -colorIdx; }
326
341
  setColor(vec4(palette[colorIdx], 1));
342
+ setPointMarkerBorderWidth(0.0);
327
343
  }
328
344
  `;
329
345
  expectShaderEqual(result, expected);
@@ -352,6 +368,7 @@ void main() {
352
368
  float g = hashToFloat(pointIndex, 1);
353
369
  float b = hashToFloat(pointIndex, 2);
354
370
  setColor(vec4(r, g, b, 0.9));
371
+ setPointMarkerBorderWidth(0.0);
355
372
  }
356
373
  `;
357
374
  expectShaderEqual(result, expected);
@@ -386,8 +403,10 @@ void main() {
386
403
  float g = hashToFloat(pointIndex, 1);
387
404
  float b = hashToFloat(pointIndex, 2);
388
405
  setColor(vec4(r, g, b, 0.5));
406
+ setPointMarkerBorderWidth(0.0);
389
407
  } else {
390
408
  setColor(vec4(0.39215686274509803, 0.39215686274509803, 0.39215686274509803, 0.5));
409
+ setPointMarkerBorderWidth(0.0);
391
410
  }
392
411
  }
393
412
  `;
@@ -425,6 +444,7 @@ void main() {
425
444
  float g = hashToFloat(pointIndex, 1);
426
445
  float b = hashToFloat(pointIndex, 2);
427
446
  setColor(vec4(r, g, b, 1));
447
+ setPointMarkerBorderWidth(0.0);
428
448
  }
429
449
  `;
430
450
  expectShaderEqual(result, expected);
@@ -101,6 +101,7 @@ export function customIsEqualForCellColors(prevDeps, nextDeps) {
101
101
  'obsSetSelection',
102
102
  'additionalObsSets',
103
103
  'spatialChannelColor',
104
+ 'spatialChannelOpacity',
104
105
  ])
105
106
  ) {
106
107
  forceUpdate = true;
@@ -128,6 +129,10 @@ export function customIsEqualForInitialViewerState(prevDeps, nextDeps) {
128
129
  const curriedShallowDiffByChannelCoordination = (depName, layerScope, channelScope) => shallowDiffByChannelCoordination(prevDeps, nextDeps, depName, layerScope, channelScope);
129
130
  const curriedShallowDiffByChannelCoordinationWithKeys = (depName, layerScope, channelScope, keys) => shallowDiffByChannelCoordinationWithKeys(prevDeps, nextDeps, depName, layerScope, channelScope, keys);
130
131
 
132
+ if (['theme', 'showAxisLines'].some(curriedShallowDiff)) {
133
+ forceUpdate = true;
134
+ }
135
+
131
136
  // Segmentation layers/channels.
132
137
  if (['segmentationLayerScopes', 'segmentationChannelScopesByLayer'].some(curriedShallowDiff)) {
133
138
  // Force update for all layers since the layerScopes array changed.
@@ -172,6 +177,7 @@ export function customIsEqualForInitialViewerState(prevDeps, nextDeps) {
172
177
  'featureSelection',
173
178
  'featureFilterMode',
174
179
  'featureColor',
180
+ 'spatialPointStrokeWidth',
175
181
  ])
176
182
  // For opacity, use an epsilon comparison to avoid too many re-renders, as it affects performance.
177
183
  || (