@vitessce/all 4.0.0 → 4.0.2

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.
@@ -5440,12 +5440,12 @@ function baseIsEqual$3(value2, other, bitmask, customizer, stack2) {
5440
5440
  var now$4 = function() {
5441
5441
  return root$d.Date.now();
5442
5442
  };
5443
- var FUNC_ERROR_TEXT$2 = "Expected a function";
5443
+ var FUNC_ERROR_TEXT$3 = "Expected a function";
5444
5444
  var nativeMax$3 = Math.max, nativeMin$1 = Math.min;
5445
5445
  function debounce$5(func, wait2, options) {
5446
5446
  var lastArgs, lastThis, maxWait, result, timerId, lastCallTime, lastInvokeTime = 0, leading = false, maxing = false, trailing = true;
5447
5447
  if (typeof func != "function") {
5448
- throw new TypeError(FUNC_ERROR_TEXT$2);
5448
+ throw new TypeError(FUNC_ERROR_TEXT$3);
5449
5449
  }
5450
5450
  wait2 = toNumber$3(wait2) || 0;
5451
5451
  if (isObject$f(options)) {
@@ -5603,6 +5603,22 @@ var range$e = createRange();
5603
5603
  function sum$6(array2) {
5604
5604
  return array2 && array2.length ? baseSum(array2, identity$h) : 0;
5605
5605
  }
5606
+ var FUNC_ERROR_TEXT$2 = "Expected a function";
5607
+ function throttle$1(func, wait2, options) {
5608
+ var leading = true, trailing = true;
5609
+ if (typeof func != "function") {
5610
+ throw new TypeError(FUNC_ERROR_TEXT$2);
5611
+ }
5612
+ if (isObject$f(options)) {
5613
+ leading = "leading" in options ? !!options.leading : leading;
5614
+ trailing = "trailing" in options ? !!options.trailing : trailing;
5615
+ }
5616
+ return debounce$5(func, wait2, {
5617
+ "leading": leading,
5618
+ "maxWait": wait2,
5619
+ "trailing": trailing
5620
+ });
5621
+ }
5606
5622
  var INFINITY = 1 / 0;
5607
5623
  var createSet = !(Set$3 && 1 / setToArray$2(new Set$3([, -0]))[1] == INFINITY) ? noop$h : function(values3) {
5608
5624
  return new Set$3(values3);
@@ -9436,6 +9452,7 @@ const CoordinationType$1 = {
9436
9452
  SPATIAL_CHANNEL_LABELS_VISIBLE: "spatialChannelLabelsVisible",
9437
9453
  SPATIAL_CHANNEL_LABELS_ORIENTATION: "spatialChannelLabelsOrientation",
9438
9454
  SPATIAL_CHANNEL_LABEL_SIZE: "spatialChannelLabelSize",
9455
+ SPATIAL_CHANNELS_SORT_ORDER: "spatialChannelsSortOrder",
9439
9456
  // Multi-sample / comparative
9440
9457
  SAMPLE_TYPE: "sampleType",
9441
9458
  SAMPLE_SELECTION: "sampleSelection",
@@ -9488,7 +9505,9 @@ const ViewHelpMapping = {
9488
9505
  OBS_SET_COMPOSITION_BAR_PLOT: "The set composition bar plot displays the results of a compositional analysis conducted using the scCODA method (Büttner et al. 2021 Nature Communications).",
9489
9506
  FEATURE_SET_ENRICHMENT_BAR_PLOT: "The feature set enrichment bar plot displays the results of a hypergeometric test applied to the differential expression test results to identify enriched pathway gene sets.",
9490
9507
  SAMPLE_SET_PAIR_MANAGER: "Select pairs of sample groups.",
9491
- FEATURE_STATS_TABLE: "This table displays per-feature statistics, for example, from a differential expression test."
9508
+ FEATURE_STATS_TABLE: "This table displays per-feature statistics, for example, from a differential expression test.",
9509
+ LAYER_CONTROLLER_BETA: "The spatial layer controller provides an interface for manipulating the visualization layers displayed in the spatial view.",
9510
+ SPATIAL_BETA: "The spatial view displays (potentially layered) spatially-resolved data including RGB or multiplexed images, segmentations of observations (bitmask- or polygon-based), and/or points (e.g., representing FISH transcripts)."
9492
9511
  };
9493
9512
  ({
9494
9513
  // For new file types
@@ -10039,6 +10058,7 @@ const COMPONENT_COORDINATION_TYPES = {
10039
10058
  CoordinationType$1.SPATIAL_CHANNEL_LABELS_VISIBLE,
10040
10059
  CoordinationType$1.SPATIAL_CHANNEL_LABELS_ORIENTATION,
10041
10060
  CoordinationType$1.SPATIAL_CHANNEL_LABEL_SIZE,
10061
+ CoordinationType$1.SPATIAL_CHANNELS_SORT_ORDER,
10042
10062
  CoordinationType$1.PHOTOMETRIC_INTERPRETATION
10043
10063
  ],
10044
10064
  [ViewType$1.HEATMAP]: [
@@ -10220,7 +10240,8 @@ const COMPONENT_COORDINATION_TYPES = {
10220
10240
  CoordinationType$1.LEGEND_VISIBLE,
10221
10241
  CoordinationType$1.SPATIAL_CHANNEL_LABELS_VISIBLE,
10222
10242
  CoordinationType$1.SPATIAL_CHANNEL_LABELS_ORIENTATION,
10223
- CoordinationType$1.SPATIAL_CHANNEL_LABEL_SIZE
10243
+ CoordinationType$1.SPATIAL_CHANNEL_LABEL_SIZE,
10244
+ CoordinationType$1.SPATIAL_CHANNELS_SORT_ORDER
10224
10245
  ],
10225
10246
  [ViewType$1.GENOMIC_PROFILES]: [
10226
10247
  CoordinationType$1.DATASET,
@@ -10931,7 +10952,18 @@ const ngPrecomputedMeshSchema = z.object({
10931
10952
  const ngPointAnnotationSchema = z.object({
10932
10953
  projectionAnnotationSpacing: z.number(),
10933
10954
  featureIndexProp: z.string().optional().describe("The name of the Neuroglancer AnnotationProperty containing feature IDs. For example, specify 'gene' to use prop_gene() in the Neuroglancer shader code."),
10934
- pointIndexProp: z.string().optional().describe("The name of the Neuroglancer AnnotationProperty containing point IDs. For example, specify 'point_id' to use prop_point_id() in the Neuroglancer shader code.")
10955
+ pointIndexProp: z.string().optional().describe("The name of the Neuroglancer AnnotationProperty containing point IDs. For example, specify 'point_id' to use prop_point_id() in the Neuroglancer shader code."),
10956
+ transform: z.object({
10957
+ matrix: z.array(z.array(z.number())),
10958
+ outputDimensions: z.record(z.tuple([z.number(), z.string()]))
10959
+ }).optional().describe("A coordinate transformation matrix to apply to the annotation layer source to map annotation coordinates to the global coordinate space."),
10960
+ // Note: See comments in neuroglancer-sorger.js
10961
+ // We may remove these in the future, pending clarification
10962
+ // to questions in the tissue-map-tools repo
10963
+ // - https://github.com/hms-dbmi/tissue-map-tools/issues/20
10964
+ // - https://github.com/hms-dbmi/tissue-map-tools/issues/38
10965
+ quantitativeColorProp: z.string().optional(),
10966
+ quantitativeColorMax: z.number().optional()
10935
10967
  }).partial().nullable();
10936
10968
  const obsEmbeddingAnndataSchema = annDataObsEmbedding;
10937
10969
  const obsSpotsAnndataSchema = annDataObsLocations;
@@ -18344,7 +18376,7 @@ const { tss } = createTss({
18344
18376
  },
18345
18377
  "usePlugin": useMuiThemeStyleOverridesPlugin
18346
18378
  });
18347
- const useStyles$x = tss.create({});
18379
+ const useStyles$y = tss.create({});
18348
18380
  function createChainedFunction$1(...funcs) {
18349
18381
  return funcs.reduce((acc, func) => {
18350
18382
  if (func == null) {
@@ -72632,7 +72664,7 @@ const Popper = React.forwardRef((props, ref2) => {
72632
72664
  }, [handlePopperRef]);
72633
72665
  React.useImperativeHandle(popperRefProp, () => popperRef.current, []);
72634
72666
  const [exited, setExited] = React.useState(true);
72635
- const { theme } = useStyles$x();
72667
+ const { theme } = useStyles$y();
72636
72668
  const rtlPlacement = flipPlacement(initialPlacement, theme);
72637
72669
  const [placement, setPlacement] = React.useState(rtlPlacement);
72638
72670
  React.useEffect(() => {
@@ -73366,7 +73398,7 @@ var VariableSizeList$1 = /* @__PURE__ */ createListComponent({
73366
73398
  });
73367
73399
  const VariableSizeList = VariableSizeList$1;
73368
73400
  const LIST_ROW_HEIGHT$1 = 48;
73369
- const useStyles$w = makeStyles()(() => ({
73401
+ const useStyles$x = makeStyles()(() => ({
73370
73402
  searchInput: {
73371
73403
  lineHeight: "initial",
73372
73404
  height: "auto !important"
@@ -73406,7 +73438,7 @@ const ListboxComponent$1 = React__default.forwardRef((props, ref2) => {
73406
73438
  });
73407
73439
  function SimpleAutocomplete(props) {
73408
73440
  const { getMatches, onChange, textInputLabel = "Enter a gene", getItemLabel = (option) => option.label } = props;
73409
- const { classes: classes2 } = useStyles$w();
73441
+ const { classes: classes2 } = useStyles$x();
73410
73442
  const [potentialItems, setPotentialItems] = useState([]);
73411
73443
  const handleInputChange = async (_event, value2) => {
73412
73444
  if (!value2) {
@@ -82956,7 +82988,7 @@ function defaultUrlTransform(value2) {
82956
82988
  }
82957
82989
  return "";
82958
82990
  }
82959
- const useStyles$v = makeStyles()((theme) => ({
82991
+ const useStyles$w = makeStyles()((theme) => ({
82960
82992
  description: {
82961
82993
  "& p, details, table": {
82962
82994
  fontSize: "80%",
@@ -82993,7 +83025,7 @@ const useStyles$v = makeStyles()((theme) => ({
82993
83025
  }));
82994
83026
  function Description(props) {
82995
83027
  const { description: description2, metadata: metadata2, descriptionType } = props;
82996
- const { classes: classes2 } = useStyles$v();
83028
+ const { classes: classes2 } = useStyles$w();
82997
83029
  return jsxs("div", { className: classes2.description, children: [descriptionType && descriptionType === DescriptionType.MARKDOWN ? jsx$1(Markdown, { children: description2 }) : jsx$1("p", { children: description2 }), metadata2 && Array.from(metadata2.entries()).map(([layerIndex, { name: layerName, metadata: metadataRecord }]) => metadataRecord && Object.entries(metadataRecord).length > 0 ? jsxs("details", { children: [jsx$1("summary", { children: layerName }), jsx$1("div", { className: classes2.metadataContainer, children: jsx$1("table", { children: jsx$1("tbody", { children: Object.entries(metadataRecord).map(([key2, value2]) => jsxs("tr", { children: [jsx$1("th", { title: key2, children: key2 }), jsx$1("td", { title: value2, children: value2 })] }, key2)) }) }) })] }, layerIndex) : null)] });
82998
83030
  }
82999
83031
  function DescriptionSubscriber(props) {
@@ -91245,6 +91277,37 @@ function dataToCellSetsTree(data2, options) {
91245
91277
  return cellSetsTree;
91246
91278
  }
91247
91279
  const schemeRdBu = [[103, 0, 31], [178, 24, 43], [214, 96, 77], [244, 165, 130], [253, 219, 199], [247, 247, 247], [209, 229, 240], [146, 197, 222], [67, 147, 195], [33, 102, 172], [5, 48, 97]];
91280
+ const schemePlasma = [[13, 8, 135], [16, 7, 136], [19, 7, 137], [22, 7, 138], [25, 6, 140], [27, 6, 141], [29, 6, 142], [32, 6, 143], [34, 6, 144], [36, 6, 145], [38, 5, 145], [40, 5, 146], [42, 5, 147], [44, 5, 148], [46, 5, 149], [47, 5, 150], [49, 5, 151], [51, 5, 151], [53, 4, 152], [55, 4, 153], [56, 4, 154], [58, 4, 154], [60, 4, 155], [62, 4, 156], [63, 4, 156], [65, 4, 157], [67, 3, 158], [68, 3, 158], [70, 3, 159], [72, 3, 159], [73, 3, 160], [75, 3, 161], [76, 2, 161], [78, 2, 162], [80, 2, 162], [81, 2, 163], [83, 2, 163], [85, 2, 164], [86, 1, 164], [88, 1, 164], [89, 1, 165], [91, 1, 165], [92, 1, 166], [94, 1, 166], [96, 1, 166], [97, 0, 167], [99, 0, 167], [100, 0, 167], [102, 0, 167], [103, 0, 168], [105, 0, 168], [106, 0, 168], [108, 0, 168], [110, 0, 168], [111, 0, 168], [113, 0, 168], [114, 1, 168], [116, 1, 168], [117, 1, 168], [119, 1, 168], [120, 1, 168], [122, 2, 168], [123, 2, 168], [125, 3, 168], [126, 3, 168], [128, 4, 168], [129, 4, 167], [131, 5, 167], [132, 5, 167], [134, 6, 166], [135, 7, 166], [136, 8, 166], [138, 9, 165], [139, 10, 165], [141, 11, 165], [142, 12, 164], [143, 13, 164], [145, 14, 163], [146, 15, 163], [148, 16, 162], [149, 17, 161], [150, 19, 161], [152, 20, 160], [153, 21, 159], [154, 22, 159], [156, 23, 158], [157, 24, 157], [158, 25, 157], [160, 26, 156], [161, 27, 155], [162, 29, 154], [163, 30, 154], [165, 31, 153], [166, 32, 152], [167, 33, 151], [168, 34, 150], [170, 35, 149], [171, 36, 148], [172, 38, 148], [173, 39, 147], [174, 40, 146], [176, 41, 145], [177, 42, 144], [178, 43, 143], [179, 44, 142], [180, 46, 141], [181, 47, 140], [182, 48, 139], [183, 49, 138], [184, 50, 137], [186, 51, 136], [187, 52, 136], [188, 53, 135], [189, 55, 134], [190, 56, 133], [191, 57, 132], [192, 58, 131], [193, 59, 130], [194, 60, 129], [195, 61, 128], [196, 62, 127], [197, 64, 126], [198, 65, 125], [199, 66, 124], [200, 67, 123], [201, 68, 122], [202, 69, 122], [203, 70, 121], [204, 71, 120], [204, 73, 119], [205, 74, 118], [206, 75, 117], [207, 76, 116], [208, 77, 115], [209, 78, 114], [210, 79, 113], [211, 81, 113], [212, 82, 112], [213, 83, 111], [213, 84, 110], [214, 85, 109], [215, 86, 108], [216, 87, 107], [217, 88, 106], [218, 90, 106], [218, 91, 105], [219, 92, 104], [220, 93, 103], [221, 94, 102], [222, 95, 101], [222, 97, 100], [223, 98, 99], [224, 99, 99], [225, 100, 98], [226, 101, 97], [226, 102, 96], [227, 104, 95], [228, 105, 94], [229, 106, 93], [229, 107, 93], [230, 108, 92], [231, 110, 91], [231, 111, 90], [232, 112, 89], [233, 113, 88], [233, 114, 87], [234, 116, 87], [235, 117, 86], [235, 118, 85], [236, 119, 84], [237, 121, 83], [237, 122, 82], [238, 123, 81], [239, 124, 81], [239, 126, 80], [240, 127, 79], [240, 128, 78], [241, 129, 77], [241, 131, 76], [242, 132, 75], [243, 133, 75], [243, 135, 74], [244, 136, 73], [244, 137, 72], [245, 139, 71], [245, 140, 70], [246, 141, 69], [246, 143, 68], [247, 144, 68], [247, 145, 67], [247, 147, 66], [248, 148, 65], [248, 149, 64], [249, 151, 63], [249, 152, 62], [249, 154, 62], [250, 155, 61], [250, 156, 60], [250, 158, 59], [251, 159, 58], [251, 161, 57], [251, 162, 56], [252, 163, 56], [252, 165, 55], [252, 166, 54], [252, 168, 53], [252, 169, 52], [253, 171, 51], [253, 172, 51], [253, 174, 50], [253, 175, 49], [253, 177, 48], [253, 178, 47], [253, 180, 47], [253, 181, 46], [254, 183, 45], [254, 184, 44], [254, 186, 44], [254, 187, 43], [254, 189, 42], [254, 190, 42], [254, 192, 41], [253, 194, 41], [253, 195, 40], [253, 197, 39], [253, 198, 39], [253, 200, 39], [253, 202, 38], [253, 203, 38], [252, 205, 37], [252, 206, 37], [252, 208, 37], [252, 210, 37], [251, 211, 36], [251, 213, 36], [251, 215, 36], [250, 216, 36], [250, 218, 36], [249, 220, 36], [249, 221, 37], [248, 223, 37], [248, 225, 37], [247, 226, 37], [247, 228, 37], [246, 230, 38], [246, 232, 38], [245, 233, 38], [245, 235, 39], [244, 237, 39], [243, 238, 39], [243, 240, 39], [242, 242, 39], [241, 244, 38], [241, 245, 37], [240, 247, 36], [240, 249, 33]];
91281
+ const schemeViridis = [[68, 1, 84], [68, 2, 86], [69, 4, 87], [69, 5, 89], [70, 7, 90], [70, 8, 92], [70, 10, 93], [70, 11, 94], [71, 13, 96], [71, 14, 97], [71, 16, 99], [71, 17, 100], [71, 19, 101], [72, 20, 103], [72, 22, 104], [72, 23, 105], [72, 24, 106], [72, 26, 108], [72, 27, 109], [72, 28, 110], [72, 30, 111], [72, 31, 112], [72, 32, 113], [72, 34, 115], [72, 35, 116], [72, 36, 117], [72, 38, 118], [72, 39, 119], [72, 40, 120], [72, 42, 121], [72, 43, 122], [72, 44, 123], [72, 46, 124], [72, 47, 125], [72, 48, 126], [72, 50, 127], [72, 51, 128], [72, 52, 129], [72, 53, 130], [72, 55, 131], [72, 56, 132], [72, 57, 133], [72, 59, 134], [72, 60, 135], [72, 61, 136], [72, 63, 137], [72, 64, 137], [72, 65, 138], [72, 67, 139], [72, 68, 140], [72, 69, 141], [72, 71, 142], [72, 72, 143], [72, 73, 144], [72, 75, 145], [72, 76, 146], [72, 77, 147], [72, 79, 148], [72, 80, 149], [72, 82, 150], [72, 83, 150], [72, 84, 151], [72, 86, 152], [72, 87, 153], [72, 88, 154], [72, 90, 155], [71, 91, 156], [71, 92, 157], [71, 94, 158], [71, 95, 158], [71, 97, 159], [71, 98, 160], [71, 99, 161], [71, 101, 162], [71, 102, 163], [71, 103, 164], [71, 105, 164], [71, 106, 165], [71, 108, 166], [71, 109, 167], [71, 110, 168], [71, 112, 169], [71, 113, 169], [71, 114, 170], [71, 116, 171], [71, 117, 172], [70, 119, 173], [70, 120, 174], [70, 121, 174], [70, 123, 175], [70, 124, 176], [70, 125, 177], [70, 127, 178], [70, 128, 178], [70, 130, 179], [70, 131, 180], [70, 132, 181], [70, 134, 182], [70, 135, 182], [70, 136, 183], [70, 138, 184], [70, 139, 185], [70, 141, 186], [70, 142, 186], [70, 143, 187], [70, 145, 188], [70, 146, 189], [70, 148, 189], [70, 149, 190], [70, 150, 191], [70, 152, 192], [70, 153, 192], [70, 155, 193], [70, 156, 194], [70, 157, 195], [70, 159, 195], [70, 160, 196], [70, 162, 197], [70, 163, 198], [70, 164, 198], [70, 166, 199], [70, 167, 200], [71, 169, 200], [71, 170, 201], [71, 171, 202], [71, 173, 203], [71, 174, 203], [71, 176, 204], [71, 177, 205], [71, 178, 205], [72, 180, 206], [72, 181, 207], [72, 183, 207], [72, 184, 208], [73, 185, 209], [73, 187, 209], [73, 188, 210], [74, 190, 211], [74, 191, 211], [74, 192, 212], [75, 194, 213], [75, 195, 213], [76, 197, 214], [76, 198, 214], [77, 199, 215], [77, 201, 216], [78, 202, 216], [78, 204, 217], [79, 205, 217], [79, 207, 218], [80, 208, 218], [81, 209, 219], [81, 211, 220], [82, 212, 220], [83, 214, 221], [83, 215, 221], [84, 216, 222], [85, 218, 222], [85, 219, 223], [86, 221, 223], [87, 222, 224], [88, 224, 224], [88, 225, 225], [89, 226, 225], [90, 228, 226], [91, 229, 226], [92, 231, 227], [92, 232, 227], [93, 234, 228], [94, 235, 228], [95, 236, 229], [96, 238, 229], [97, 239, 230], [97, 241, 230], [98, 242, 231], [99, 244, 231], [100, 245, 232], [101, 246, 232], [102, 248, 233], [103, 249, 233], [104, 251, 234], [105, 252, 234]];
91282
+ const schemeJet = Array.from({ length: 256 }, (_, i2) => {
91283
+ const t4 = i2 / 255;
91284
+ let r3;
91285
+ let g2;
91286
+ let b2;
91287
+ if (t4 < 0.125) {
91288
+ r3 = 0;
91289
+ g2 = 0;
91290
+ b2 = 0.5 + t4 * 4;
91291
+ } else if (t4 < 0.375) {
91292
+ r3 = 0;
91293
+ g2 = (t4 - 0.125) * 4;
91294
+ b2 = 1;
91295
+ } else if (t4 < 0.625) {
91296
+ r3 = (t4 - 0.375) * 4;
91297
+ g2 = 1;
91298
+ b2 = 1 - (t4 - 0.375) * 4;
91299
+ } else if (t4 < 0.875) {
91300
+ r3 = 1;
91301
+ g2 = 1 - (t4 - 0.625) * 4;
91302
+ b2 = 0;
91303
+ } else {
91304
+ r3 = 1 - (t4 - 0.875) * 4;
91305
+ g2 = 0;
91306
+ b2 = 0;
91307
+ }
91308
+ return [Math.round(r3 * 255), Math.round(g2 * 255), Math.round(b2 * 255)];
91309
+ });
91310
+ const schemeGreys = Array.from({ length: 256 }, (_, i2) => [i2, i2, i2]);
91248
91311
  function rgbSpline$1(spline) {
91249
91312
  return (colors2) => {
91250
91313
  const n3 = colors2.length;
@@ -91282,7 +91345,15 @@ function basis$2(values3) {
91282
91345
  };
91283
91346
  }
91284
91347
  const interpolateRgbBasis = rgbSpline$1(basis$2);
91348
+ function interpolateSequentialMulti(range2) {
91349
+ const n3 = range2.length;
91350
+ return (t4) => range2[Math.max(0, Math.min(n3 - 1, Math.floor(t4 * n3)))];
91351
+ }
91285
91352
  interpolateRgbBasis(schemeRdBu);
91353
+ const interpolatePlasma = interpolateSequentialMulti(schemePlasma);
91354
+ const interpolateViridis = interpolateSequentialMulti(schemeViridis);
91355
+ const interpolateJet = interpolateSequentialMulti(schemeJet);
91356
+ const interpolateGreys = interpolateSequentialMulti(schemeGreys);
91286
91357
  function getCellColors(params2) {
91287
91358
  const { cellSets, cellSetSelection, cellSetColor, obsIndex, theme } = params2;
91288
91359
  if (cellSetSelection && cellSets) {
@@ -98064,7 +98135,7 @@ const Tooltip$2 = /* @__PURE__ */ React.forwardRef((props, ref2) => {
98064
98135
  }, extraProps), getChildren());
98065
98136
  });
98066
98137
  const nodeHeight = 32;
98067
- const useStyles$u = makeStyles()((theme) => ({
98138
+ const useStyles$v = makeStyles()((theme) => ({
98068
98139
  setsManager: {
98069
98140
  position: "relative",
98070
98141
  width: "100%",
@@ -98231,7 +98302,7 @@ const useStyles$u = makeStyles()((theme) => ({
98231
98302
  }));
98232
98303
  function SetsManagerTreeGlobalStyles(props) {
98233
98304
  const { classes: classes2 } = props;
98234
- const { theme } = useStyles$x();
98305
+ const { theme } = useStyles$y();
98235
98306
  return jsx$1(ScopedGlobalStyles, { parentClassName: classes2.setsManagerTree, styles: {
98236
98307
  ".rc-tree": {
98237
98308
  paddingLeft: "0",
@@ -98572,7 +98643,7 @@ function HelpTooltipGlobalStyles(props) {
98572
98643
  }
98573
98644
  function PopoverGlobalStyles(props) {
98574
98645
  const { classes: classes2 } = props;
98575
- const { theme } = useStyles$x();
98646
+ const { theme } = useStyles$y();
98576
98647
  return jsx$1(ScopedGlobalStyles, { parentClassName: classes2.popover, styles: {
98577
98648
  ".rc-tooltip-inner": {
98578
98649
  boxSizing: "border-box",
@@ -103256,7 +103327,7 @@ function NamedSetNodeStatic(props) {
103256
103327
  const onClick = level === 0 && !expanded ? () => onCheckLevel(nodeKey, nextLevelToCheck) : () => onNodeView(path2);
103257
103328
  const tooltipProps = disableTooltip ? { visible: false } : {};
103258
103329
  const popoverMenuConfig = makeNodeViewMenuConfig(props);
103259
- const { classes: classes2 } = useStyles$u();
103330
+ const { classes: classes2 } = useStyles$v();
103260
103331
  return jsxs("span", { children: [jsx$1(HelpTooltip, { title: tooltipText, ...tooltipProps, children: jsx$1("button", { type: "button", onClick, onKeyPress: (e3) => callbackOnKeyPress(e3, "v", () => onNodeView(path2)), className: classes2.titleButton, children: title2 }) }), popoverMenuConfig.length > 0 ? jsx$1(PopoverMenu, { menuConfig: makeNodeViewMenuConfig(props), color: level > 0 && editable ? color2 || getDefaultColor(theme) : null, setColor: (c2) => onNodeSetColor(path2, c2), children: jsx$1("span", { children: jsx$1(SvgMenu, { className: classes2.nodeMenuIcon, "aria-label": "Open Node View Menu" }) }) }) : null, level > 0 && isChecking ? checkbox2 : null, level > 0 && jsx$1("span", { className: classes2.nodeSizeLabel, children: niceSize })] });
103261
103332
  }
103262
103333
  function NamedSetNodeEditing(props) {
@@ -103268,7 +103339,7 @@ function NamedSetNodeEditing(props) {
103268
103339
  onNodeSetName(path2, currentTitle, true);
103269
103340
  }
103270
103341
  }
103271
- const { classes: classes2 } = useStyles$u();
103342
+ const { classes: classes2 } = useStyles$v();
103272
103343
  return jsxs("span", { className: classes2.titleButtonWithInput, children: [jsx$1("input", {
103273
103344
  // eslint-disable-next-line jsx-a11y/no-autofocus
103274
103345
  autoFocus: true,
@@ -103294,7 +103365,7 @@ function LevelsButtons(props) {
103294
103365
  onCheckLevel(nodeKey, newLevel);
103295
103366
  }
103296
103367
  }
103297
- const { classes: classes2 } = useStyles$u();
103368
+ const { classes: classes2 } = useStyles$v();
103298
103369
  return jsx$1("div", { className: classes2.levelButtonsContainer, children: range$e(1, height2 + 1).map((i2) => {
103299
103370
  const isChecked = isEqual$3(path2, checkedLevelPath) && i2 === checkedLevelIndex;
103300
103371
  return jsx$1("div", { children: jsx$1(HelpTooltip, { title: getLevelTooltipText(i2), children: jsx$1("input", { className: clsx(classes2.levelRadioButton, { [classes2.levelRadioButtonChecked]: isChecked && !hasColorEncoding }), type: "checkbox", value: i2, checked: isChecked && hasColorEncoding, onChange: onCheck }) }) }, i2);
@@ -103378,7 +103449,7 @@ let TreeNode$1 = class TreeNode extends TreeNode$2 {
103378
103449
  };
103379
103450
  function PlusButton(props) {
103380
103451
  const { datatype, onError, onImportTree, onCreateLevelZeroNode, importable, editable } = props;
103381
- const { classes: classes2 } = useStyles$u();
103452
+ const { classes: classes2 } = useStyles$v();
103382
103453
  const onImport = useCallback((importHandler, mimeType) => () => {
103383
103454
  const uploadInputNode = document.createElement("input");
103384
103455
  uploadInputNode.setAttribute("type", "file");
@@ -103495,7 +103566,7 @@ function SetsManager(props) {
103495
103566
  }, onNodeCheckNewName, onNodeSetIsEditing: setIsEditingNodeName, onNodeRemove, onExportLevelZeroNodeJSON, onExportLevelZeroNodeTabular, onExportSetJSON, disableTooltip: isDragging, onDragStart: () => setIsDragging(true), onDragEnd: () => setIsDragging(false), children: renderTreeNodes(node2.children, readOnly, newPath) }, pathToKey(newPath));
103496
103567
  });
103497
103568
  }
103498
- const { classes: classes2 } = useStyles$u();
103569
+ const { classes: classes2 } = useStyles$v();
103499
103570
  return jsxs("div", { className: classes2.setsManager, children: [jsx$1(SetsManagerTreeGlobalStyles, { classes: classes2 }), jsxs("div", { className: classes2.setsManagerTree, children: [jsx$1(Tree$1, { draggable: false, checkable, checkedKeys: setSelectionKeys, expandedKeys: setExpansionKeys, autoExpandParent, onCheck: (checkedKeys, info2) => onCheckNode(info2.node.props.nodeKey, info2.checked), onExpand: (expandedKeys, info2) => onExpandNode(expandedKeys, info2.node.props.nodeKey, info2.expanded), children: renderTreeNodes(processedSets.tree, true, []) }), jsx$1(Tree$1, { draggable: true, checkable, checkedKeys: additionalSetSelectionKeys, expandedKeys: additionalSetExpansionKeys, autoExpandParent, onCheck: (checkedKeys, info2) => onCheckNode(info2.node.props.nodeKey, info2.checked), onExpand: (expandedKeys, info2) => onExpandNode(expandedKeys, info2.node.props.nodeKey, info2.expanded), onDrop: (info2) => {
103500
103571
  const { eventKey: dropKey } = info2.node.props;
103501
103572
  const { eventKey: dragKey } = info2.dragNode.props;
@@ -209460,22 +209531,22 @@ async function getDecoder(fileDirectory) {
209460
209531
  const Decoder = await importFn();
209461
209532
  return new Decoder(fileDirectory);
209462
209533
  }
209463
- addDecoder([void 0, 1], () => import("./raw-BX1y7Iy_.js").then((m2) => m2.default));
209464
- addDecoder(5, () => import("./lzw-DJhX6Tr-.js").then((m2) => m2.default));
209534
+ addDecoder([void 0, 1], () => import("./raw-UWcztCkw.js").then((m2) => m2.default));
209535
+ addDecoder(5, () => import("./lzw-xVcb3-tK.js").then((m2) => m2.default));
209465
209536
  addDecoder(6, () => {
209466
209537
  throw new Error("old style JPEG compression is not supported.");
209467
209538
  });
209468
- addDecoder(7, () => import("./jpeg-CYgQz5aG.js").then((m2) => m2.default));
209469
- addDecoder([8, 32946], () => import("./deflate-VdN3pn_b.js").then((m2) => m2.default));
209470
- addDecoder(32773, () => import("./packbits-BOwAxrqR.js").then((m2) => m2.default));
209539
+ addDecoder(7, () => import("./jpeg-9VJS3-Bw.js").then((m2) => m2.default));
209540
+ addDecoder([8, 32946], () => import("./deflate-BnG3V84V.js").then((m2) => m2.default));
209541
+ addDecoder(32773, () => import("./packbits-Dm4fdK8S.js").then((m2) => m2.default));
209471
209542
  addDecoder(
209472
209543
  34887,
209473
- () => import("./lerc-C-Llf71G.js").then(async (m2) => {
209544
+ () => import("./lerc-D1J26SPX.js").then(async (m2) => {
209474
209545
  await m2.zstd.init();
209475
209546
  return m2;
209476
209547
  }).then((m2) => m2.default)
209477
209548
  );
209478
- addDecoder(50001, () => import("./webimage-DCU5-f3Q.js").then((m2) => m2.default));
209549
+ addDecoder(50001, () => import("./webimage-C-QWVV-f.js").then((m2) => m2.default));
209479
209550
  function copyNewSize(array2, width2, height2, samplesPerPixel = 1) {
209480
209551
  return new (Object.getPrototypeOf(array2)).constructor(width2 * height2 * samplesPerPixel);
209481
209552
  }
@@ -226249,7 +226320,7 @@ class ContourLayerWithText extends ContourLayer {
226249
226320
  }
226250
226321
  ContourLayerWithText.layerName = "ContourLayerWithText";
226251
226322
  ContourLayerWithText.defaultProps = defaultProps;
226252
- const useStyles$t = makeStyles()(() => ({
226323
+ const useStyles$u = makeStyles()(() => ({
226253
226324
  toolButton: {
226254
226325
  display: "inline-flex",
226255
226326
  "&:active": {
@@ -226311,12 +226382,12 @@ const useStyles$t = makeStyles()(() => ({
226311
226382
  }));
226312
226383
  function IconTool(props) {
226313
226384
  const { alt, onClick, isActive: isActive2, children: children3 } = props;
226314
- const { classes: classes2 } = useStyles$t();
226385
+ const { classes: classes2 } = useStyles$u();
226315
226386
  return jsx$1("button", { className: clsx(classes2.toolIcon, { [classes2.toolActive]: isActive2 }), onClick, type: "button", title: alt, children: children3 });
226316
226387
  }
226317
226388
  function IconButton(props) {
226318
226389
  const { alt, onClick, children: children3 } = props;
226319
- const { classes: classes2 } = useStyles$t();
226390
+ const { classes: classes2 } = useStyles$u();
226320
226391
  return jsx$1("button", { className: clsx(classes2.toolIcon, classes2.toolButton), onClick, type: "button", title: alt, children: children3 });
226321
226392
  }
226322
226393
  function ToolMenu(props) {
@@ -226324,14 +226395,14 @@ function ToolMenu(props) {
226324
226395
  const lassoIconAltText = "Select lasso";
226325
226396
  const { setActiveTool, activeTool, visibleTools = { pan: true, selectLasso: true, recenter: true }, recenterOnClick = () => {
226326
226397
  } } = props;
226327
- const { classes: classes2 } = useStyles$t();
226398
+ const { classes: classes2 } = useStyles$u();
226328
226399
  const onRecenterButtonCLick = () => {
226329
226400
  recenterOnClick();
226330
226401
  };
226331
226402
  return jsxs("div", { className: classes2.tool, children: [visibleTools.pan && jsx$1(IconTool, { alt: pointerIconAltText, onClick: () => setActiveTool(null), isActive: activeTool === null, children: jsx$1(SvgNearMe, {}) }), visibleTools.selectLasso ? jsx$1(IconTool, { alt: lassoIconAltText, onClick: () => setActiveTool(distEs6Exports$2.SELECTION_TYPE.POLYGON), isActive: activeTool === distEs6Exports$2.SELECTION_TYPE.POLYGON, children: jsx$1(SvgSelectionLasso, {}) }) : null, visibleTools.recenter ? jsx$1(IconButton, { alt: "click to recenter", onClick: () => onRecenterButtonCLick(), "aria-label": "Recenter scatterplot view", children: jsx$1(CenterFocusStrong, {}) }) : null] });
226332
226403
  }
226333
226404
  const getCursorWithTool = () => "crosshair";
226334
- const getCursor = (interactionState) => interactionState.isDragging ? "grabbing" : "default";
226405
+ const getCursor = (interactionState) => interactionState.isDragging ? "grabbing" : "grab";
226335
226406
  function getOnHoverCallback(obsIndex, setObsHighlight, setComponentHover) {
226336
226407
  return (info2) => {
226337
226408
  if (setComponentHover) {
@@ -227481,7 +227552,7 @@ function ScatterplotOptions(props) {
227481
227552
  id: `feature-aggregation-strategy-${scatterplotOptionsId}`
227482
227553
  }, children: FEATURE_AGGREGATION_STRATEGIES$2.map((opt) => jsx$1("option", { value: opt, children: capitalize$3(opt) }, opt)) }) })] }) : null] });
227483
227554
  }
227484
- const useStyles$s = makeStyles()((theme) => ({
227555
+ const useStyles$t = makeStyles()((theme) => ({
227485
227556
  tooltipAnchor: {
227486
227557
  position: "relative",
227487
227558
  width: "0px",
@@ -227512,7 +227583,7 @@ const useStyles$s = makeStyles()((theme) => ({
227512
227583
  function Tooltip3(props) {
227513
227584
  const { x: x2, y: y2, parentWidth, parentHeight, children: children3 } = props;
227514
227585
  const ref2 = useRef();
227515
- const { classes: classes2 } = useStyles$s();
227586
+ const { classes: classes2 } = useStyles$t();
227516
227587
  const [placementX, setPlacementX] = useState("start");
227517
227588
  const [placementY, setPlacementY] = useState("bottom");
227518
227589
  const getTooltipContainer = useVitessceContainer(ref2);
@@ -227528,7 +227599,7 @@ function Tooltip3(props) {
227528
227599
  }, [x2, y2, parentWidth, parentHeight]);
227529
227600
  return jsx$1("div", { ref: ref2, className: classes2.tooltipAnchor, children: ref2 && ref2.current ? jsx$1(Popper, { open: true, anchorEl: ref2.current, container: getTooltipContainer, transition: true, placement: `${placementY}-${placementX}`, children: jsx$1(Paper, { elevation: 8, className: classes2.tooltipContent, children: children3 }) }) : null });
227530
227601
  }
227531
- const useStyles$r = makeStyles()((theme) => ({
227602
+ const useStyles$s = makeStyles()((theme) => ({
227532
227603
  cellEmphasisCrosshair: {
227533
227604
  zIndex: 50,
227534
227605
  position: "absolute",
@@ -227539,7 +227610,7 @@ const useStyles$r = makeStyles()((theme) => ({
227539
227610
  }));
227540
227611
  function Tooltip2D(props) {
227541
227612
  const { parentUuid, sourceUuid, x: x2, y: y2, parentWidth, parentHeight, children: children3 } = props;
227542
- const { classes: classes2 } = useStyles$r();
227613
+ const { classes: classes2 } = useStyles$s();
227543
227614
  if (x2 < 0 || x2 > parentWidth || y2 < 0 || y2 > parentHeight) {
227544
227615
  return null;
227545
227616
  }
@@ -231451,7 +231522,7 @@ function getXlinkHref(cmap) {
231451
231522
  const xlinkHref = ramp(color2.copy().domain(quantize$3(interpolate$1$1(0, 1), n3))).toDataURL();
231452
231523
  return xlinkHref;
231453
231524
  }
231454
- const useStyles$q = makeStyles()(() => ({
231525
+ const useStyles$r = makeStyles()(() => ({
231455
231526
  legend: {
231456
231527
  position: "relative",
231457
231528
  // Needed for absolute positioning of slider overlay
@@ -231648,7 +231719,7 @@ function Legend(props) {
231648
231719
  contourThresholds
231649
231720
  } = props;
231650
231721
  const svgRef = useRef(null);
231651
- const { classes: classes2 } = useStyles$q();
231722
+ const { classes: classes2 } = useStyles$r();
231652
231723
  const [localRange, setLocalRange] = useState(featureValueColormapRange);
231653
231724
  useEffect(() => {
231654
231725
  setLocalRange(featureValueColormapRange);
@@ -231982,7 +232053,7 @@ function Legend(props) {
231982
232053
  }
231983
232054
  } })] })] });
231984
232055
  }
231985
- const useStyles$p = makeStyles()(() => ({
232056
+ const useStyles$q = makeStyles()(() => ({
231986
232057
  multiLegend: {
231987
232058
  position: "absolute",
231988
232059
  top: "0px",
@@ -232009,7 +232080,7 @@ function MultiLegend(props) {
232009
232080
  pointLayerCoordination,
232010
232081
  pointMultiIndicesData
232011
232082
  } = props;
232012
- const { classes: classes2 } = useStyles$p();
232083
+ const { classes: classes2 } = useStyles$q();
232013
232084
  const reversedSegmentationLayerScopes = useMemo$1(() => [...segmentationLayerScopes || []].reverse(), [segmentationLayerScopes]);
232014
232085
  const reversedSpotLayerScopes = useMemo$1(() => [...spotLayerScopes || []].reverse(), [spotLayerScopes]);
232015
232086
  const reversedPointLayerScopes = useMemo$1(() => [...pointLayerScopes || []].reverse(), [pointLayerScopes]);
@@ -232102,7 +232173,7 @@ function MultiLegend(props) {
232102
232173
  }) : null;
232103
232174
  }) : null] });
232104
232175
  }
232105
- const useStyles$o = makeStyles()(() => ({
232176
+ const useStyles$p = makeStyles()(() => ({
232106
232177
  channelNamesLegendContainer: {
232107
232178
  position: "absolute",
232108
232179
  bottom: "0px",
@@ -232125,7 +232196,7 @@ const useStyles$o = makeStyles()(() => ({
232125
232196
  }));
232126
232197
  function ChannelNamesLegend(props) {
232127
232198
  const { images, imageLayerScopes, imageLayerCoordination, imageChannelScopesByLayer, imageChannelCoordination } = props;
232128
- const { classes: classes2 } = useStyles$o();
232199
+ const { classes: classes2 } = useStyles$p();
232129
232200
  const reversedImageLayerScopes = useMemo$1(() => [...imageLayerScopes || []].reverse(), [imageLayerScopes]);
232130
232201
  return jsx$1("div", { className: classes2.channelNamesLegendContainer, children: imageLayerScopes ? reversedImageLayerScopes.map((layerScope) => {
232131
232202
  const layerCoordination = imageLayerCoordination[0][layerScope];
@@ -253669,7 +253740,7 @@ class Spatial2 extends AbstractSpatialOrScatterplot {
253669
253740
  this.viewInfoDidUpdate();
253670
253741
  const shallowDiff2 = (propName) => prevProps[propName] !== this.props[propName];
253671
253742
  const shallowDiffByLayer2 = (propName, scopeName) => prevProps?.[propName]?.[scopeName] !== this.props?.[propName]?.[scopeName];
253672
- const shallowDiffByChannel = (propName, firstName, secondName) => prevProps?.[propName]?.[firstName]?.[secondName] !== this.props?.[propName]?.[firstName]?.[secondName];
253743
+ const shallowDiffByChannel2 = (propName, firstName, secondName) => prevProps?.[propName]?.[firstName]?.[secondName] !== this.props?.[propName]?.[firstName]?.[secondName];
253673
253744
  const shallowDiffByLayerCoordination = (propName, layerScope) => prevProps?.[propName]?.[0]?.[layerScope] !== this.props?.[propName]?.[0]?.[layerScope];
253674
253745
  const shallowDiffByChannelCoordination = (propName, layerScope, channelScope) => prevProps?.[propName]?.[0]?.[layerScope]?.[channelScope] !== this.props?.[propName]?.[0]?.[layerScope]?.[channelScope];
253675
253746
  let forceUpdate = false;
@@ -253690,7 +253761,7 @@ class Spatial2 extends AbstractSpatialOrScatterplot {
253690
253761
  } else {
253691
253762
  this.props.segmentationLayerScopes?.forEach((layerScope) => {
253692
253763
  this.props.segmentationChannelScopesByLayer?.[layerScope]?.forEach((channelScope) => {
253693
- if (shallowDiffByChannel("obsSegmentationsLocations", layerScope, channelScope)) {
253764
+ if (shallowDiffByChannel2("obsSegmentationsLocations", layerScope, channelScope)) {
253694
253765
  this.onUpdateSegmentationsLocationsData(layerScope, channelScope);
253695
253766
  forceUpdate = true;
253696
253767
  }
@@ -253703,7 +253774,7 @@ class Spatial2 extends AbstractSpatialOrScatterplot {
253703
253774
  } else {
253704
253775
  this.props.segmentationLayerScopes?.forEach((layerScope) => {
253705
253776
  this.props.segmentationChannelScopesByLayer?.[layerScope]?.forEach((channelScope) => {
253706
- if (shallowDiffByChannel("obsSegmentationsSets", layerScope, channelScope) || shallowDiffByChannelCoordination("segmentationChannelCoordination", layerScope, channelScope)) {
253777
+ if (shallowDiffByChannel2("obsSegmentationsSets", layerScope, channelScope) || shallowDiffByChannelCoordination("segmentationChannelCoordination", layerScope, channelScope)) {
253707
253778
  this.onUpdateSegmentationsSetsData(layerScope, channelScope);
253708
253779
  forceUpdate = true;
253709
253780
  }
@@ -253716,7 +253787,7 @@ class Spatial2 extends AbstractSpatialOrScatterplot {
253716
253787
  } else {
253717
253788
  this.props.segmentationLayerScopes?.forEach((layerScope) => {
253718
253789
  this.props.segmentationChannelScopesByLayer?.[layerScope]?.forEach((channelScope) => {
253719
- if (shallowDiffByChannel("segmentationMatrixIndices", layerScope, channelScope)) {
253790
+ if (shallowDiffByChannel2("segmentationMatrixIndices", layerScope, channelScope)) {
253720
253791
  this.onUpdateSegmentationsIndexData(layerScope, channelScope);
253721
253792
  forceUpdate = true;
253722
253793
  }
@@ -253729,7 +253800,7 @@ class Spatial2 extends AbstractSpatialOrScatterplot {
253729
253800
  } else {
253730
253801
  this.props.segmentationLayerScopes.forEach((layerScope) => {
253731
253802
  this.props.segmentationChannelScopesByLayer?.[layerScope]?.forEach((channelScope) => {
253732
- if (shallowDiffByChannel("segmentationMatrixIndices", layerScope, channelScope) || shallowDiffByChannel("segmentationMultiExpressionData", layerScope, channelScope)) {
253803
+ if (shallowDiffByChannel2("segmentationMatrixIndices", layerScope, channelScope) || shallowDiffByChannel2("segmentationMultiExpressionData", layerScope, channelScope)) {
253733
253804
  this.onUpdateSegmentationsExpressionData(layerScope, channelScope);
253734
253805
  forceUpdate = true;
253735
253806
  }
@@ -253983,12 +254054,12 @@ class ErrorBoundary extends React__default.Component {
253983
254054
  }
253984
254055
  }
253985
254056
  const LazySpatialThree = React__default.lazy(async () => {
253986
- const { SpatialWrapper: SpatialWrapper2 } = await import("./index-tp3-aWWe.js").then((n3) => n3.a);
254057
+ const { SpatialWrapper: SpatialWrapper2 } = await import("./index-CwGBUQIk.js").then((n3) => n3.a);
253987
254058
  return { default: SpatialWrapper2 };
253988
254059
  });
253989
254060
  const SpatialThreeAdapter = React__default.forwardRef((props, ref2) => jsx$1("div", { ref: ref2, style: { width: "100%", height: "100%" }, children: jsx$1(ErrorBoundary, { fallback: jsx$1("div", { children: "3D spatial view is not available in this environment." }), children: jsx$1(Suspense, { fallback: jsx$1("div", { children: "Loading..." }), children: jsx$1(LazySpatialThree, { ...props }) }) }) }));
253990
254061
  const LazySpatialAccelerated = React__default.lazy(async () => {
253991
- const { SpatialWrapper: SpatialWrapper2 } = await import("./index-Dau2mo3I.js");
254062
+ const { SpatialWrapper: SpatialWrapper2 } = await import("./index-B9h9IQaz.js");
253992
254063
  return { default: SpatialWrapper2 };
253993
254064
  });
253994
254065
  const SpatialAcceleratedAdapter = React__default.forwardRef((props, ref2) => jsx$1("div", { ref: ref2, style: { width: "100%", height: "100%" }, children: jsx$1(ErrorBoundary, { fallback: jsx$1("div", { children: "3D spatial view is not available in this environment." }), children: jsx$1(Suspense, { fallback: jsx$1("div", { children: "Loading..." }), children: jsx$1(LazySpatialAccelerated, { ...props }) }) }) }));
@@ -254165,7 +254236,8 @@ function SpatialSubscriber(props) {
254165
254236
  bitmaskValueIsIndex = false,
254166
254237
  // TODO: move to coordination type
254167
254238
  three: threeFor3d = false,
254168
- accelerated: acceleratedFor3d = false
254239
+ accelerated: acceleratedFor3d = false,
254240
+ helpText = ViewHelpMapping.SPATIAL_BETA
254169
254241
  } = props;
254170
254242
  const loaders = useLoaders();
254171
254243
  const setComponentHover = useSetComponentHover();
@@ -254609,7 +254681,7 @@ function SpatialSubscriber(props) {
254609
254681
  }
254610
254682
  }
254611
254683
  };
254612
- return jsxs(TitleInfo, { title: title2, info: subtitle, isSpatial: true, urls: urls2, theme, closeButtonVisible, downloadButtonVisible, removeGridComponent, isReady, errors, children: [shouldUseThree ? acceleratedFor3d ? jsx$1(SpatialAcceleratedAdapter, { ref: threeRef, uuid: uuid2, width: width2, height: height2, theme, hideTools: !isSelectable, rotation: [rotationX, rotationY, rotationZ], setRotationX, setRotationY, setRotationZ, targetT, targetZ, viewState: isValidViewState ? {
254684
+ return jsxs(TitleInfo, { title: title2, info: subtitle, isSpatial: true, urls: urls2, theme, closeButtonVisible, downloadButtonVisible, removeGridComponent, isReady, errors, helpText, children: [shouldUseThree ? acceleratedFor3d ? jsx$1(SpatialAcceleratedAdapter, { ref: threeRef, uuid: uuid2, width: width2, height: height2, theme, hideTools: !isSelectable, rotation: [rotationX, rotationY, rotationZ], setRotationX, setRotationY, setRotationZ, targetT, targetZ, viewState: isValidViewState ? {
254613
254685
  zoom: zoom2,
254614
254686
  target: [targetX2, targetY2, targetZ],
254615
254687
  rotationX,
@@ -262060,7 +262132,7 @@ _defineProperty$3(WindowScroller, "defaultProps", {
262060
262132
  serverHeight: 0,
262061
262133
  serverWidth: 0
262062
262134
  });
262063
- const useStyles$n = makeStyles()((theme) => ({
262135
+ const useStyles$o = makeStyles()((theme) => ({
262064
262136
  selectableTable: {
262065
262137
  flex: "1 1 auto",
262066
262138
  outline: "none"
@@ -262209,7 +262281,7 @@ function SelectableTable(props) {
262209
262281
  onChange(null);
262210
262282
  }
262211
262283
  }, [selectedRows, allowMultiple]);
262212
- const { classes: classes2 } = useStyles$n();
262284
+ const { classes: classes2 } = useStyles$o();
262213
262285
  const inputUuid = v4$1();
262214
262286
  const rowRenderer = ({ index: index2, style: style2 }) => (
262215
262287
  // eslint-disable-next-line jsx-a11y/interactive-supports-focus
@@ -262234,7 +262306,7 @@ const FEATURELIST_SORT_OPTIONS = [
262234
262306
  "original"
262235
262307
  ];
262236
262308
  const ALT_COLNAME = "Alternate ID";
262237
- const useStyles$m = makeStyles()(() => ({
262309
+ const useStyles$n = makeStyles()(() => ({
262238
262310
  searchBar: {
262239
262311
  marginTop: "10px",
262240
262312
  marginLeft: "10px",
@@ -262247,7 +262319,7 @@ const useStyles$m = makeStyles()(() => ({
262247
262319
  }));
262248
262320
  function FeatureList(props) {
262249
262321
  const { width: width2, height: height2, hasColorEncoding, geneList = [], featureLabelsMap, geneSelection = [], geneFilter = null, setGeneSelection, enableMultiSelect, showFeatureTable, featureListSort, featureListSortKey, hasFeatureLabels, primaryColumnName } = props;
262250
- const { classes: classes2 } = useStyles$m();
262322
+ const { classes: classes2 } = useStyles$n();
262251
262323
  const [searchTerm, setSearchTerm] = useState("");
262252
262324
  const selectableTableSortKey = featureListSortKey === "featureIndex" ? "key" : "name";
262253
262325
  const searchResults = useMemo$1(() => geneList.filter((gene) => gene.toLowerCase().includes(searchTerm.toLowerCase()) || featureLabelsMap?.get(gene)?.toLowerCase().includes(searchTerm.toLowerCase()) || featureLabelsMap?.get(cleanFeatureId(gene))?.toLowerCase().includes(searchTerm.toLowerCase())), [geneList, searchTerm, featureLabelsMap]);
@@ -262312,7 +262384,7 @@ function FeatureListOptions(props) {
262312
262384
  const { classes: classes2 } = usePlotOptionsStyles();
262313
262385
  return jsxs(OptionsContainer, { children: [children3, jsxs(TableRow, { children: [jsx$1(TableCell, { className: classes2.labelCell, variant: "head", scope: "row", children: jsx$1("label", { htmlFor: `feature-list-sort-option-${featureListId}`, children: "Sort Ordering" }) }), jsx$1(TableCell, { variant: "body", children: jsx$1(OptionSelect, { className: classes2.select, value: featureListSort, onChange: handleFeatureListSortChange, inputProps: {
262314
262386
  id: `feature-list-sort-option-${featureListId}`
262315
- }, children: FEATURELIST_SORT_OPTIONS.map((option) => jsx$1("option", { value: option, children: option }, option)) }) })] }), hasFeatureLabels ? jsxs(Fragment, { children: [jsxs(TableRow, { children: [jsx$1(TableCell, { className: classes2.labelCell, variant: "head", scope: "row", children: jsx$1("label", { htmlFor: `feature-list-sort-key-${featureListId}`, children: "Sort Key" }) }), jsx$1(TableCell, { variant: "body", children: jsx$1(OptionSelect, { className: classes2.select, disabled: featureListSort === "original", value: featureListSortKey, onChange: handleFeatureListSortKeyChange, inputProps: {
262387
+ }, children: FEATURELIST_SORT_OPTIONS.map((option) => jsx$1("option", { value: option, children: capitalize$3(option) }, option)) }) })] }), hasFeatureLabels ? jsxs(Fragment, { children: [jsxs(TableRow, { children: [jsx$1(TableCell, { className: classes2.labelCell, variant: "head", scope: "row", children: jsx$1("label", { htmlFor: `feature-list-sort-key-${featureListId}`, children: "Sort Key" }) }), jsx$1(TableCell, { variant: "body", children: jsx$1(OptionSelect, { className: classes2.select, disabled: featureListSort === "original", value: featureListSortKey, onChange: handleFeatureListSortKeyChange, inputProps: {
262316
262388
  "aria-label": "Select the feature list sort key",
262317
262389
  id: `feature-list-sort-key-${featureListId}`
262318
262390
  }, children: hasFeatureLabels ? jsxs(Fragment, { children: [jsx$1("option", { value: "featureLabels", children: primaryColumnName }), jsx$1("option", { value: "featureIndex", children: ALT_COLNAME })] }) : jsx$1("option", { value: "featureIndex", children: primaryColumnName }) }) })] }), jsxs(TableRow, { children: [jsx$1(TableCell, { className: classes2.labelCell, variant: "head", scope: "row", children: jsx$1("label", { htmlFor: `feature-list-show-alternative-ids-${featureListId}`, children: "Show Alternate IDs" }) }), jsx$1(TableCell, { className: classes2.inputCell, variant: "body", children: jsx$1(Checkbox$1, { className: classes2.tableCheckbox, checked: showFeatureTable, onChange: handleShowTableChange, name: "feature-list-show-table", color: "default", slotProps: { input: {
@@ -262320,7 +262392,7 @@ function FeatureListOptions(props) {
262320
262392
  id: `feature-list-show-alternative-ids-${featureListId}`
262321
262393
  } } }) })] })] }) : null] });
262322
262394
  }
262323
- const useStyles$l = makeStyles()((theme) => ({
262395
+ const useStyles$m = makeStyles()((theme) => ({
262324
262396
  featureListContainer: {
262325
262397
  width: "100%",
262326
262398
  height: "100%",
@@ -262335,7 +262407,7 @@ function FeatureListSubscriber(props) {
262335
262407
  const loaders = useLoaders();
262336
262408
  const coordinationScopes = useCoordinationScopes(coordinationScopesRaw);
262337
262409
  const [width2, height2, containerRef] = useGridItemSize();
262338
- const { classes: classes2 } = useStyles$l();
262410
+ const { classes: classes2 } = useStyles$m();
262339
262411
  const [{ dataset, obsType, featureType, featureSelection: geneSelection, featureFilter: geneFilter, obsColorEncoding: cellColorEncoding }, { setFeatureSelection: setGeneSelection, setFeatureFilter: setGeneFilter, setFeatureHighlight: setGeneHighlight, setObsColorEncoding: setCellColorEncoding }] = useCoordination(COMPONENT_COORDINATION_TYPES[ViewType$1.FEATURE_LIST], coordinationScopes);
262340
262412
  const variablesLabel = variablesLabelOverride || featureType;
262341
262413
  const title2 = titleOverride || `${capitalize$3(variablesLabel)} List`;
@@ -262470,7 +262542,7 @@ const useSelectionSliderStyles = makeStyles()(() => ({
262470
262542
  backgroundColor: "rgba(128, 128, 128, 0.7)"
262471
262543
  }
262472
262544
  }));
262473
- const useStyles$k = makeStyles()((theme) => ({
262545
+ const useStyles$l = makeStyles()((theme) => ({
262474
262546
  paletteContainer: {
262475
262547
  width: "70px",
262476
262548
  height: "40px",
@@ -262491,10 +262563,10 @@ const useStyles$k = makeStyles()((theme) => ({
262491
262563
  }
262492
262564
  }));
262493
262565
  const ColorPalette = ({ handleChange }) => {
262494
- const { classes: classes2 } = useStyles$k();
262566
+ const { classes: classes2 } = useStyles$l();
262495
262567
  return jsx$1("div", { className: classes2.paletteContainer, "aria-label": "Color swatch", children: VIEWER_PALETTE.map((color2) => jsx$1(IconButton$1, { className: classes2.button, onClick: () => handleChange(color2), "aria-label": `Change color to ${color2}`, children: jsx$1(LensIcon, { fontSize: "small", style: { color: `rgb(${color2})` }, className: classes2.icon }) }, color2)) });
262496
262568
  };
262497
- const useStyles$j = makeStyles()(() => ({
262569
+ const useStyles$k = makeStyles()(() => ({
262498
262570
  menuButton: {
262499
262571
  backgroundColor: "transparent"
262500
262572
  },
@@ -262513,7 +262585,7 @@ function MuiSpan(props) {
262513
262585
  }
262514
262586
  function ChannelOptions$1({ handlePropertyChange, handleChannelRemove, handleIQRUpdate }) {
262515
262587
  const [open2, setOpen] = useState(false);
262516
- const { classes: classes2 } = useStyles$j();
262588
+ const { classes: classes2 } = useStyles$k();
262517
262589
  const handleColorSelect = (color2) => {
262518
262590
  handlePropertyChange("color", color2);
262519
262591
  };
@@ -263130,7 +263202,7 @@ function LayerController$1(props) {
263130
263202
  minWidth: "50%"
263131
263203
  } })] }), jsx$1(TabPanel, { value: tab2, index: 0, children: FullController }), jsx$1(TabPanel, { value: tab2, index: 1, style: { marginTop: 4 }, children: jsx$1(VolumeOptions, { loader: loader2, handleSlicerSetting, handleRenderingModeChange: setRenderingMode, renderingMode, xSlice, ySlice, zSlice, use3d, setViewState, spatialHeight, spatialWidth, modelMatrix: modelMatrix2 }) })] }) : FullController, shouldShowRemoveLayerButton ? jsx$1(Button, { onClick: handleLayerRemove, fullWidth: true, variant: "outlined", style: buttonStyles, size: "small", children: "Remove Image Layer" }) : null] })] });
263132
263204
  }
263133
- const useStyles$i = makeStyles()(() => ({
263205
+ const useStyles$j = makeStyles()(() => ({
263134
263206
  addButton: {
263135
263207
  marginTop: "10px",
263136
263208
  marginBottom: "10px",
@@ -263142,7 +263214,7 @@ function ImageAddIcon() {
263142
263214
  }
263143
263215
  function ImageAddButton({ imageOptions, handleImageAdd }) {
263144
263216
  const [open2, setOpen] = useState(false);
263145
- const { classes: classes2 } = useStyles$i();
263217
+ const { classes: classes2 } = useStyles$j();
263146
263218
  const handleAdd = (imgData) => {
263147
263219
  setOpen((prev2) => !prev2);
263148
263220
  handleImageAdd(imgData);
@@ -263474,7 +263546,7 @@ const useEllipsisMenuStyles = makeStyles()(() => ({
263474
263546
  padding: "4px"
263475
263547
  }
263476
263548
  }));
263477
- const useStyles$h = makeStyles()(() => ({
263549
+ const useStyles$i = makeStyles()(() => ({
263478
263550
  colorPickerPaper: {
263479
263551
  overflow: "hidden",
263480
263552
  "& > ul": {
@@ -263532,7 +263604,7 @@ function ChannelColorPickerMenu(props) {
263532
263604
  setColor2([rgb2.r, rgb2.g, rgb2.b]);
263533
263605
  }
263534
263606
  }
263535
- const { classes: classes2 } = useStyles$h();
263607
+ const { classes: classes2 } = useStyles$i();
263536
263608
  const currentColor = color2 ? colorArrayToString(color2) : colorArrayToString(getDefaultColor(theme));
263537
263609
  const isWritable = typeof setColor2 === "function";
263538
263610
  const derivedOpen = isWritable ? isStaticColor && visible ? open2 : false : false;
@@ -263540,7 +263612,7 @@ function ChannelColorPickerMenu(props) {
263540
263612
  [classes2.colorIconOutline]: theme !== "dark" && isEqual$3(color2, [255, 255, 255])
263541
263613
  }), style: { backgroundColor: currentColor } }) : isColormap && visible && featureValueColormap ? jsx$1("div", { className: classes2.colorIcon, children: jsx$1("svg", { width: "18", height: "18", children: jsx$1("image", { x: 0, y: 0, width: 18, height: 18, preserveAspectRatio: "none", href: getXlinkHref(featureValueColormap), clipPath: "inset(0% round 4px)" }) }) }) : jsx$1("div", { className: classes2.colorIcon }), buttonClassName: classes2.colorIconButton, withPaper: true, paperClassName: classes2.colorPickerPaper, "aria-label": "Open color picker menu", children: jsx$1(Twitter$1, { className: classes2.colorPicker, disableAlpha: true, width: 108, triangle: "hide", colors: defaultPalette, color: currentColor, onChangeComplete: handleColorChange }) });
263542
263614
  }
263543
- const useStyles$g = makeStyles()(() => ({
263615
+ const useStyles$h = makeStyles()(() => ({
263544
263616
  layerTypeSpotIcon: {
263545
263617
  height: "100%",
263546
263618
  marginLeft: "1px",
@@ -263553,7 +263625,7 @@ const useStyles$g = makeStyles()(() => ({
263553
263625
  function SpotLayerEllipsisMenu(props) {
263554
263626
  const { strokeWidth, setStrokeWidth, filled, setFilled, featureSelection, obsColorEncoding, setObsColorEncoding, featureValueColormap, setFeatureValueColormap, featureValueColormapRange, setFeatureValueColormapRange, tooltipsVisible, setTooltipsVisible, tooltipCrosshairsVisible, setTooltipCrosshairsVisible, legendVisible, setLegendVisible } = props;
263555
263627
  const [open2, setOpen] = useState(false);
263556
- const { classes: classes2 } = useStyles$g();
263628
+ const { classes: classes2 } = useStyles$h();
263557
263629
  const { classes: selectClasses2 } = useSelectStyles();
263558
263630
  const { classes: menuClasses } = useEllipsisMenuStyles();
263559
263631
  const filledId = $bdb11010cef70236$export$f680877a34711e37();
@@ -263575,7 +263647,7 @@ function SpotLayerController(props) {
263575
263647
  const Visibility = useMemo$1(() => visibleSetting ? VisibilityIcon : VisibilityOffIcon, [visibleSetting]);
263576
263648
  const isStaticColor = obsColorEncoding === "spatialLayerColor";
263577
263649
  const isColormap = obsColorEncoding === "geneSelection";
263578
- const { classes: classes2 } = useStyles$g();
263650
+ const { classes: classes2 } = useStyles$h();
263579
263651
  const { classes: lcClasses } = useControllerSectionStyles();
263580
263652
  const { classes: menuClasses } = useEllipsisMenuStyles();
263581
263653
  const handleVisibleChange = useCallback(() => {
@@ -263585,7 +263657,7 @@ function SpotLayerController(props) {
263585
263657
  const handleOpacityChange = useCallback((e3, v) => setOpacity2(v), [setOpacity2]);
263586
263658
  return jsx$1(Grid$1, { className: lcClasses.layerControllerGrid, children: jsx$1(Paper, { elevation: 4, className: lcClasses.layerControllerRoot, children: jsxs(Grid$1, { container: true, direction: "row", justifyContent: "space-between", children: [jsx$1(Grid$1, { size: 1, children: jsx$1(Button, { onClick: handleVisibleChange, className: menuClasses.imageLayerVisibleButton, "aria-label": "Toggle spot layer visibility", children: jsx$1(Visibility, {}) }) }), jsx$1(Grid$1, { size: 1, children: jsx$1(ChannelColorPickerMenu, { theme, color: color2, setColor: setColor2, palette, isStaticColor, isColormap, featureValueColormap, visible }) }), jsx$1(Grid$1, { size: 6, children: jsx$1(Typography, { className: menuClasses.imageLayerName, children: label2 }) }), jsx$1(Grid$1, { size: 2, children: jsx$1(Slider, { value: opacity2, min: 0, max: 1, step: 1e-3, onChange: handleOpacityChange, className: menuClasses.imageLayerOpacitySlider, orientation: "horizontal", "aria-label": `Adjust opacity for layer ${label2}` }) }), jsx$1(Grid$1, { size: 1, children: jsx$1(SpotLayerEllipsisMenu, { filled, setFilled, strokeWidth, setStrokeWidth, featureSelection, obsColorEncoding, setObsColorEncoding, featureValueColormap, setFeatureValueColormap, featureValueColormapRange, setFeatureValueColormapRange, tooltipsVisible, setTooltipsVisible, tooltipCrosshairsVisible, setTooltipCrosshairsVisible, legendVisible, setLegendVisible }) }), jsx$1(Grid$1, { size: 1, children: jsx$1(SvgSpots, { className: classes2.layerTypeSpotIcon }) })] }) }) });
263587
263659
  }
263588
- const useStyles$f = makeStyles()(() => ({
263660
+ const useStyles$g = makeStyles()(() => ({
263589
263661
  layerFeatureControllerGrid: {
263590
263662
  padding: "0",
263591
263663
  flexWrap: "nowrap"
@@ -263630,14 +263702,15 @@ function LayerPerFeatureController(props) {
263630
263702
  ]);
263631
263703
  }
263632
263704
  }, [featureName, featureColor, setFeatureColor, featureColorIndex]);
263633
- const { classes: classes2 } = useStyles$f();
263705
+ const { classes: classes2 } = useStyles$g();
263634
263706
  const { classes: lcClasses } = useControllerSectionStyles();
263635
263707
  const { classes: menuClasses } = useEllipsisMenuStyles();
263636
263708
  const [open2, setOpen] = useState(false);
263637
263709
  const { colorPickerColor, colorPickerReadable, colorPickerWritable, colorPickerTooltip } = useMemo$1(() => {
263638
263710
  if (obsColorEncoding === "geneSelection") {
263711
+ const storedColor = featureColor?.find((fc) => fc.name === featureName)?.color;
263639
263712
  return {
263640
- colorPickerColor: featureColor?.[featureColorIndex]?.color ?? spatialLayerColor,
263713
+ colorPickerColor: storedColor ?? randomByFeatureColor ?? spatialLayerColor,
263641
263714
  colorPickerReadable: true,
263642
263715
  colorPickerWritable: true,
263643
263716
  colorPickerTooltip: null
@@ -263683,7 +263756,7 @@ function LayerPerFeatureController(props) {
263683
263756
  ]);
263684
263757
  return jsx$1(Grid$1, { className: lcClasses.layerControllerGrid, children: jsxs(Paper, { elevation: 2, className: lcClasses.layerControllerSubRow, children: [jsxs(Grid$1, { container: true, direction: "row", justifyContent: "space-between", children: [jsx$1(Grid$1, { size: 1, children: jsx$1(Button, { onClick: handleRemoveFeature, className: menuClasses.imageLayerVisibleButton, "aria-label": "Remove feature", children: jsx$1(ClearIcon, {}) }) }), jsx$1(Grid$1, { size: 1, children: jsx$1(Tooltip$3, { title: colorPickerTooltip ?? "", disableHoverListener: !colorPickerTooltip, placement: "top", children: jsx$1("span", { children: jsx$1(ChannelColorPickerMenu, { theme, color: colorPickerColor, setColor: colorPickerWritable ? handleColorChange : null, palette, isStaticColor: colorPickerReadable, isColormap: false, featureValueColormap, visible: true }) }) }) }), jsx$1(Grid$1, { size: 6, children: jsx$1(Typography, { className: menuClasses.imageLayerName, children: featureName }) }), jsx$1(Grid$1, { size: 2, sx: { paddingRight: "12px", overflow: "visible" }, children: jsx$1(Slider, { value: opacity2, min: 0, max: 1, step: 1e-3, onChange: handleOpacityChange, className: menuClasses.imageLayerOpacitySlider, orientation: "horizontal", "aria-label": `Adjust opacity for layer ${featureName}` }) }), jsxs(Grid$1, { size: 1, container: true, direction: "row", children: [jsx$1(SvgPoints, { className: classes2.layerTypeFeatureIcon }), null] })] }), loadingDoneFraction < 1 ? jsx$1(Grid$1, { size: 12, container: true, direction: "column", justifyContent: "space-between", className: classes2.layerFeatureControllerGrid, children: jsx$1(LinearProgress, { variant: loadingDoneFraction === 0 ? "indeterminate" : "determinate", value: loadingDoneFraction * 100 }) }) : null] }) }, featureName);
263685
263758
  }
263686
- const useStyles$e = makeStyles()(() => ({
263759
+ const useStyles$f = makeStyles()(() => ({
263687
263760
  pointLayerButton: {
263688
263761
  borderStyle: "dashed",
263689
263762
  marginTop: "10px",
@@ -263712,7 +263785,7 @@ const useStyles$e = makeStyles()(() => ({
263712
263785
  }
263713
263786
  }));
263714
263787
  function PointLayerEllipsisMenu(props) {
263715
- const { featureSelection, featureFilterMode, setFeatureFilterMode, obsColorEncoding, setObsColorEncoding, featureValueColormapRange, setFeatureValueColormapRange, tooltipsVisible, setTooltipsVisible, tooltipCrosshairsVisible, setTooltipCrosshairsVisible, legendVisible, setLegendVisible } = props;
263788
+ const { featureSelection, featureFilterMode, setFeatureFilterMode, obsColorEncoding, setObsColorEncoding, featureValueColormap, setFeatureValueColormap, featureValueColormapRange, setFeatureValueColormapRange, tooltipsVisible, setTooltipsVisible, tooltipCrosshairsVisible, setTooltipCrosshairsVisible, legendVisible, setLegendVisible } = props;
263716
263789
  const [open2, setOpen] = useState(false);
263717
263790
  const { classes: selectClasses2 } = useSelectStyles();
263718
263791
  const { classes: menuClasses } = useEllipsisMenuStyles();
@@ -263722,7 +263795,8 @@ function PointLayerEllipsisMenu(props) {
263722
263795
  const crosshairsVisibleId = $bdb11010cef70236$export$f680877a34711e37();
263723
263796
  const legendVisibleId = $bdb11010cef70236$export$f680877a34711e37();
263724
263797
  const featureFilterModeId = $bdb11010cef70236$export$f680877a34711e37();
263725
- return jsxs(PopperMenu, { open: open2, setOpen, buttonIcon: jsx$1(MoreVertIcon, {}), buttonClassName: menuClasses.imageLayerMenuButton, containerClassName: menuClasses.imageLayerPopperContainer, withPaper: true, "aria-label": "Open point layer options menu", children: [jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: quantitativeColormapId, children: "Color Encoding: " }), jsxs(NativeSelect, { onChange: (e3) => setObsColorEncoding(e3.target.value), value: obsColorEncoding, inputProps: { id: quantitativeColormapId, "aria-label": "Color encoding selector" }, classes: { root: selectClasses2.selectRoot }, children: [jsx$1("option", { value: "spatialLayerColor", children: "Static Color" }), jsx$1("option", { value: "geneSelection", children: "Feature Color" }), jsx$1("option", { value: "randomByFeature", children: "Random Color per Feature" }), jsx$1("option", { value: "random", children: "Random Color per Point" })] })] }), jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: featureFilterModeId, children: "Filter to Feature Selection: " }), jsx$1(Checkbox$1, { color: "primary", className: menuClasses.menuItemCheckbox, checked: featureFilterMode === "featureSelection", onChange: (e3, v) => setFeatureFilterMode(v ? "featureSelection" : null), slotProps: { input: { id: featureFilterModeId, "aria-label": "Toggle feature filter mode" } } })] }), jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: colormapRangeId, children: "Colormap Range: " }), jsx$1(Slider, { disabled: obsColorEncoding !== "geneSelection", value: featureValueColormapRange, min: 0, max: 1, step: 0.01, onChange: (e3, v) => setFeatureValueColormapRange(v), className: menuClasses.menuItemSlider, orientation: "horizontal", id: colormapRangeId, getAriaLabel: (index2) => index2 === 0 ? "Low value colormap range slider" : "High value colormap range slider" })] }), jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: tooltipsVisibleId, children: "Tooltips Visible: " }), jsx$1(Checkbox$1, { color: "primary", className: menuClasses.menuItemCheckbox, checked: tooltipsVisible, onChange: (e3, v) => setTooltipsVisible(v), slotProps: { input: { id: tooltipsVisibleId, "aria-label": "Toggle tooltip visibility" } } })] }), jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: crosshairsVisibleId, children: "Tooltip Crosshairs Visible: " }), jsx$1(Checkbox$1, { color: "primary", className: menuClasses.menuItemCheckbox, checked: tooltipCrosshairsVisible, onChange: (e3, v) => setTooltipCrosshairsVisible(v), slotProps: { input: { id: crosshairsVisibleId, "aria-label": "Toggle tooltip crosshair visibility" } } })] }), jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: legendVisibleId, children: "Legend Visible: " }), jsx$1(Checkbox$1, { color: "primary", className: menuClasses.menuItemCheckbox, checked: legendVisible, onChange: (e3, v) => setLegendVisible(v), slotProps: { input: { id: legendVisibleId, "aria-label": "Toggle legend visibility" } } })] })] });
263798
+ const colormapSelectorId = $bdb11010cef70236$export$f680877a34711e37();
263799
+ return jsxs(PopperMenu, { open: open2, setOpen, buttonIcon: jsx$1(MoreVertIcon, {}), buttonClassName: menuClasses.imageLayerMenuButton, containerClassName: menuClasses.imageLayerPopperContainer, withPaper: true, getAriaLabel: () => "Open point layer options menu", children: [jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: colormapSelectorId, children: "Colormap: " }), jsx$1(NativeSelect, { onChange: (e3) => setFeatureValueColormap(e3.target.value), value: featureValueColormap, inputProps: { id: colormapSelectorId, "aria-label": "Colormap selector" }, classes: { root: selectClasses2.selectRoot }, children: GLSL_COLORMAPS.map((cmap) => jsx$1("option", { value: cmap, children: cmap }, cmap)) })] }), jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: quantitativeColormapId, children: "Color Encoding: " }), jsxs(NativeSelect, { onChange: (e3) => setObsColorEncoding(e3.target.value), value: obsColorEncoding, inputProps: { id: quantitativeColormapId, "aria-label": "Color encoding selector" }, classes: { root: selectClasses2.selectRoot }, children: [jsx$1("option", { value: "spatialLayerColor", children: "Static Color" }), jsx$1("option", { value: "geneSelection", children: "Feature Color" }), jsx$1("option", { value: "randomByFeature", children: "Random Color per Feature" }), jsx$1("option", { value: "random", children: "Random Color per Point" })] })] }), jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: featureFilterModeId, children: "Filter to Feature Selection: " }), jsx$1(Checkbox$1, { color: "primary", className: menuClasses.menuItemCheckbox, checked: featureFilterMode === "featureSelection", onChange: (e3, v) => setFeatureFilterMode(v ? "featureSelection" : null), slotProps: { input: { id: featureFilterModeId, "aria-label": "Toggle feature filter mode" } } })] }), jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: colormapRangeId, children: "Colormap Range: " }), jsx$1(Slider, { disabled: obsColorEncoding !== "geneSelection", value: featureValueColormapRange, min: 0, max: 1, step: 0.01, onChange: (e3, v) => setFeatureValueColormapRange(v), className: menuClasses.menuItemSlider, orientation: "horizontal", id: colormapRangeId, getAriaLabel: (index2) => index2 === 0 ? "Low value colormap range slider" : "High value colormap range slider" })] }), jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: tooltipsVisibleId, children: "Tooltips Visible: " }), jsx$1(Checkbox$1, { color: "primary", className: menuClasses.menuItemCheckbox, checked: tooltipsVisible, onChange: (e3, v) => setTooltipsVisible(v), slotProps: { input: { id: tooltipsVisibleId, "aria-label": "Toggle tooltip visibility" } } })] }), jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: crosshairsVisibleId, children: "Tooltip Crosshairs Visible: " }), jsx$1(Checkbox$1, { color: "primary", className: menuClasses.menuItemCheckbox, checked: tooltipCrosshairsVisible, onChange: (e3, v) => setTooltipCrosshairsVisible(v), slotProps: { input: { id: crosshairsVisibleId, "aria-label": "Toggle tooltip crosshair visibility" } } })] }), jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: legendVisibleId, children: "Legend Visible: " }), jsx$1(Checkbox$1, { color: "primary", className: menuClasses.menuItemCheckbox, checked: legendVisible, onChange: (e3, v) => setLegendVisible(v), slotProps: { input: { id: legendVisibleId, "aria-label": "Toggle legend visibility" } } })] })] });
263726
263800
  }
263727
263801
  function PointLayerController(props) {
263728
263802
  const { theme, layerScope, layerCoordination, setLayerCoordination, palette = null, pointMatrixIndicesData, tiledPointsLoadingProgress, layerPerFeatureForPoints } = props;
@@ -263752,7 +263826,7 @@ function PointLayerController(props) {
263752
263826
  }, [featureColor, featureSelection]);
263753
263827
  const isStaticColor = obsColorEncoding === "spatialLayerColor" || obsColorEncoding === "geneSelection";
263754
263828
  const showStaticColor = obsColorEncoding === "spatialLayerColor" || obsColorEncoding === "geneSelection" && hasUnspecifiedFeatureColors;
263755
- const isColormap = false;
263829
+ const isColormap = obsColorEncoding === "geneSelection";
263756
263830
  const hasSingleSelectedFeature = obsColorEncoding === "geneSelection" && Array.isArray(featureSelection) && featureSelection.length === 1;
263757
263831
  const color2 = useMemo$1(() => {
263758
263832
  if (showStaticColor) {
@@ -263799,7 +263873,7 @@ function PointLayerController(props) {
263799
263873
  featureSelection,
263800
263874
  showStaticColor
263801
263875
  ]);
263802
- const { classes: classes2 } = useStyles$e();
263876
+ const { classes: classes2 } = useStyles$f();
263803
263877
  const { classes: lcClasses } = useControllerSectionStyles();
263804
263878
  const { classes: menuClasses } = useEllipsisMenuStyles();
263805
263879
  const handleVisibleChange = useCallback(() => {
@@ -263811,9 +263885,24 @@ function PointLayerController(props) {
263811
263885
  const [coloringTabIndex, setColoringTabIndex] = useState(0);
263812
263886
  const { featureIndex } = pointMatrixIndicesData || {};
263813
263887
  const hasSelectedFeatures = Array.isArray(featureSelection) && featureSelection.length > 0;
263814
- return jsxs(Grid$1, { className: lcClasses.layerControllerGrid, children: [jsxs(Paper, { elevation: 4, className: lcClasses.layerControllerRoot, children: [jsxs(Grid$1, { container: true, direction: "row", justifyContent: "space-between", children: [jsx$1(Grid$1, { size: 1, children: jsx$1(Button, { onClick: handleVisibleChange, className: menuClasses.imageLayerVisibleButton, "aria-label": "Toggle layer visibility", children: jsx$1(Visibility, {}) }) }), jsx$1(Grid$1, { size: 1, children: jsx$1(ChannelColorPickerMenu, { theme, color: color2, setColor: setColor2, palette, isStaticColor, isColormap, featureValueColormap, visible }) }), jsx$1(Grid$1, { size: 6, children: jsx$1(Typography, { className: menuClasses.imageLayerName, children: label2 }) }), jsx$1(Grid$1, { size: 2, children: jsx$1(Slider, { value: opacity2, min: 0, max: 1, step: 1e-3, onChange: handleOpacityChange, className: menuClasses.imageLayerOpacitySlider, orientation: "horizontal", "aria-label": `Adjust opacity for layer ${label2}` }) }), jsx$1(Grid$1, { size: 1, children: jsx$1(PointLayerEllipsisMenu, { featureSelection, obsColorEncoding, setObsColorEncoding, featureValueColormapRange, setFeatureValueColormapRange, tooltipsVisible, setTooltipsVisible, tooltipCrosshairsVisible, setTooltipCrosshairsVisible, legendVisible, setLegendVisible, featureFilterMode, setFeatureFilterMode }) }), jsxs(Grid$1, { size: 1, container: true, direction: "row", children: [jsx$1(SvgPoints, { className: classes2.layerTypePointIcon }), null] })] }), loadingDoneFraction < 1 ? jsx$1(Grid$1, { size: 12, container: true, direction: "column", justifyContent: "space-between", className: classes2.pointFeatureControllerGrid, children: jsx$1(LinearProgress, { variant: loadingDoneFraction === 0 ? "indeterminate" : "determinate", value: loadingDoneFraction * 100 }) }) : null, null] }), layerPerFeatureForPoints && hasSelectedFeatures ? jsxs(Fragment, { children: [featureSelection.map((featureName) => jsx$1(LayerPerFeatureController, { theme, featureName, featureColor, setFeatureColor, spatialLayerColor, featureIndex, featureValueColormap, featureSelection, setFeatureSelection, obsColorEncoding, loadingDoneFraction, opacity: opacity2, handleOpacityChange }, featureName)), jsx$1(Grid$1, { className: lcClasses.layerControllerGrid, children: jsx$1(Paper, { elevation: 2, className: lcClasses.layerControllerSubRow, children: jsxs(Grid$1, { container: true, direction: "row", justifyContent: "space-between", children: [jsx$1(Grid$1, { size: 1, children: jsx$1(Button, { onClick: () => setFeatureFilterMode(featureFilterMode === "featureSelection" ? null : "featureSelection"), className: menuClasses.imageLayerVisibleButton, "aria-label": "Toggle visibility of unselected points", children: featureFilterMode === "featureSelection" ? jsx$1(VisibilityOffIcon, {}) : jsx$1(VisibilityIcon, {}) }) }), jsx$1(Grid$1, { size: 1, children: jsx$1(ChannelColorPickerMenu, { theme, color: getDefaultColor("dark"), setColor: null, isStaticColor: true, isColormap: false, visible: true }) }), jsx$1(Grid$1, { size: 6, children: jsx$1(Typography, { className: menuClasses.imageLayerName, children: "Unselected" }) }), jsx$1(Grid$1, { size: 2, sx: { paddingRight: "12px", overflow: "visible" }, children: jsx$1(Slider, { value: opacity2, min: 0, max: 1, step: 1e-3, onChange: handleOpacityChange, className: menuClasses.imageLayerOpacitySlider, orientation: "horizontal", "aria-label": "Adjust opacity for unselected layer" }) }), jsx$1(Grid$1, { size: 1, children: jsx$1(SvgPoints, { className: classes2.layerTypePointIcon }) })] }) }) })] }) : null] });
263888
+ useEffect(() => {
263889
+ if (obsColorEncoding !== "geneSelection" || !Array.isArray(featureSelection))
263890
+ return;
263891
+ const cleanedFeatureColor = (featureColor ?? []).filter((fc) => featureSelection.includes(fc.name));
263892
+ const missingEntries = featureSelection.filter((featureName) => !cleanedFeatureColor.find((fc) => fc.name === featureName)).map((featureName) => {
263893
+ const varIdx = featureIndex?.indexOf(featureName) ?? -1;
263894
+ const fColor = varIdx >= 0 ? PALETTE[varIdx % PALETTE.length] : getDefaultColor(theme);
263895
+ return { name: featureName, color: fColor };
263896
+ });
263897
+ const nextFeatureColor = [...cleanedFeatureColor, ...missingEntries];
263898
+ const changed = nextFeatureColor.length !== featureColor?.length || missingEntries.length > 0;
263899
+ if (changed) {
263900
+ setFeatureColor(nextFeatureColor);
263901
+ }
263902
+ }, [featureSelection, obsColorEncoding, featureIndex, featureColor]);
263903
+ return jsxs(Grid$1, { className: lcClasses.layerControllerGrid, children: [jsxs(Paper, { elevation: 4, className: lcClasses.layerControllerRoot, children: [jsxs(Grid$1, { container: true, direction: "row", justifyContent: "space-between", children: [jsx$1(Grid$1, { size: 1, children: jsx$1(Button, { onClick: handleVisibleChange, className: menuClasses.imageLayerVisibleButton, "aria-label": "Toggle layer visibility", children: jsx$1(Visibility, {}) }) }), jsx$1(Grid$1, { size: 1, children: jsx$1(ChannelColorPickerMenu, { theme, color: color2, setColor: setColor2, palette, isStaticColor, isColormap, featureValueColormap, visible }) }), jsx$1(Grid$1, { size: 6, children: jsx$1(Typography, { className: menuClasses.imageLayerName, children: label2 }) }), jsx$1(Grid$1, { size: 2, children: jsx$1(Slider, { value: opacity2, min: 0, max: 1, step: 1e-3, onChange: handleOpacityChange, className: menuClasses.imageLayerOpacitySlider, orientation: "horizontal", "aria-label": `Adjust opacity for layer ${label2}` }) }), jsx$1(Grid$1, { size: 1, children: jsx$1(PointLayerEllipsisMenu, { featureSelection, obsColorEncoding, setObsColorEncoding, featureValueColormapRange, setFeatureValueColormapRange, tooltipsVisible, setTooltipsVisible, tooltipCrosshairsVisible, setTooltipCrosshairsVisible, legendVisible, setLegendVisible, featureFilterMode, setFeatureFilterMode, featureValueColormap, setFeatureValueColormap }) }), jsxs(Grid$1, { size: 1, container: true, direction: "row", children: [jsx$1(SvgPoints, { className: classes2.layerTypePointIcon }), null] })] }), loadingDoneFraction < 1 ? jsx$1(Grid$1, { size: 12, container: true, direction: "column", justifyContent: "space-between", className: classes2.pointFeatureControllerGrid, children: jsx$1(LinearProgress, { variant: loadingDoneFraction === 0 ? "indeterminate" : "determinate", value: loadingDoneFraction * 100 }) }) : null, null] }), layerPerFeatureForPoints && hasSelectedFeatures ? jsxs(Fragment, { children: [featureSelection.map((featureName) => jsx$1(LayerPerFeatureController, { theme, featureName, featureColor, setFeatureColor, spatialLayerColor, featureIndex, featureValueColormap, featureSelection, setFeatureSelection, obsColorEncoding, loadingDoneFraction, opacity: opacity2, handleOpacityChange }, featureName)), jsx$1(Grid$1, { className: lcClasses.layerControllerGrid, children: jsx$1(Paper, { elevation: 2, className: lcClasses.layerControllerSubRow, children: jsxs(Grid$1, { container: true, direction: "row", justifyContent: "space-between", children: [jsx$1(Grid$1, { size: 1, children: jsx$1(Button, { onClick: () => setFeatureFilterMode(featureFilterMode === "featureSelection" ? null : "featureSelection"), className: menuClasses.imageLayerVisibleButton, "aria-label": "Toggle visibility of unselected points", children: featureFilterMode === "featureSelection" ? jsx$1(VisibilityOffIcon, {}) : jsx$1(VisibilityIcon, {}) }) }), jsx$1(Grid$1, { size: 1, children: jsx$1(ChannelColorPickerMenu, { theme, color: getDefaultColor("dark"), setColor: null, isStaticColor: true, isColormap: false, visible: true }) }), jsx$1(Grid$1, { size: 6, children: jsx$1(Typography, { className: menuClasses.imageLayerName, children: "Unselected" }) }), jsx$1(Grid$1, { size: 2, sx: { paddingRight: "12px", overflow: "visible" }, children: jsx$1(Slider, { value: opacity2, min: 0, max: 1, step: 1e-3, onChange: handleOpacityChange, className: menuClasses.imageLayerOpacitySlider, orientation: "horizontal", "aria-label": "Adjust opacity for unselected layer" }) }), jsx$1(Grid$1, { size: 1, children: jsx$1(SvgPoints, { className: classes2.layerTypePointIcon }) })] }) }) })] }) : null] });
263815
263904
  }
263816
- const useStyles$d = makeStyles()(() => ({
263905
+ const useStyles$e = makeStyles()(() => ({
263817
263906
  layerTypeSegmentationIcon: {
263818
263907
  height: "100%",
263819
263908
  marginLeft: "1px",
@@ -263826,7 +263915,7 @@ const useStyles$d = makeStyles()(() => ({
263826
263915
  function SegmentationChannelEllipsisMenu(props) {
263827
263916
  const { obsType, featureType, featureValueType, strokeWidth, setStrokeWidth, filled, setFilled, featureSelection, obsColorEncoding, setObsColorEncoding, featureValueColormap, setFeatureValueColormap, featureValueColormapRange, setFeatureValueColormapRange, tooltipsVisible, setTooltipsVisible, tooltipCrosshairsVisible, setTooltipCrosshairsVisible, legendVisible, setLegendVisible } = props;
263828
263917
  const [open2, setOpen] = useState(false);
263829
- const { classes: classes2 } = useStyles$d();
263918
+ const { classes: classes2 } = useStyles$e();
263830
263919
  const { classes: selectClasses2 } = useSelectStyles();
263831
263920
  const { classes: menuClasses } = useEllipsisMenuStyles();
263832
263921
  const filledId = $bdb11010cef70236$export$f680877a34711e37();
@@ -263845,7 +263934,7 @@ function SegmentationChannelController(props) {
263845
263934
  const Visibility = useMemo$1(() => visibleSetting ? VisibilityIcon : VisibilityOffIcon, [visibleSetting]);
263846
263935
  const isStaticColor = obsColorEncoding === "spatialChannelColor";
263847
263936
  const isColormap = obsColorEncoding === "geneSelection";
263848
- const { classes: classes2 } = useStyles$d();
263937
+ const { classes: classes2 } = useStyles$e();
263849
263938
  const { classes: lcClasses } = useControllerSectionStyles();
263850
263939
  const { classes: menuClasses } = useEllipsisMenuStyles();
263851
263940
  const handleVisibleChange = useCallback(() => {
@@ -265086,7 +265175,7 @@ function useBaseQuery(options, Observer, queryClient) {
265086
265175
  function useQuery(options, queryClient) {
265087
265176
  return useBaseQuery(options, QueryObserver);
265088
265177
  }
265089
- const useStyles$c = makeStyles()(() => ({
265178
+ const useStyles$d = makeStyles()(() => ({
265090
265179
  channelMenuButton: {
265091
265180
  backgroundColor: "transparent",
265092
265181
  padding: "3px 0",
@@ -265100,7 +265189,7 @@ const useStyles$c = makeStyles()(() => ({
265100
265189
  function ChannelOptions(props) {
265101
265190
  const { onRemove: onRemove2, onResetWindowUsingIQR, showValueExtent, setShowValueExtent, numResolutions, spatialMaxResolution, setSpatialMaxResolution } = props;
265102
265191
  const [open2, setOpen] = useState(false);
265103
- const { classes: classes2 } = useStyles$c();
265192
+ const { classes: classes2 } = useStyles$d();
265104
265193
  const { classes: selectClasses2 } = useSelectStyles();
265105
265194
  const { classes: menuClasses } = useEllipsisMenuStyles();
265106
265195
  function handleRemove() {
@@ -265113,7 +265202,7 @@ function ChannelOptions(props) {
265113
265202
  const domainTypeId = $bdb11010cef70236$export$f680877a34711e37();
265114
265203
  return jsxs(PopperMenu, { open: open2, setOpen, buttonIcon: jsx$1(MoreVertIcon, { fontSize: "small" }), buttonClassName: classes2.channelMenuButton, containerClassName: menuClasses.imageLayerPopperContainer, placement: "bottom-end", withPaper: true, "aria-label": "Open channel options menu", children: [jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: domainTypeId, children: "Best Resolution: " }), jsx$1(Slider, { value: spatialMaxResolution || 1, valueLabelDisplay: "auto", onChange: (e3, v) => setSpatialMaxResolution(v), min: 1, max: numResolutions - 1, step: 1, orientation: "horizontal" })] }), jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: domainTypeId, children: "Slider Extent: " }), jsxs(NativeSelect, { onChange: handleDomainTypeChange, value: showValueExtent ? "Value Min/Max" : "Dtype Min/Max", inputProps: { id: domainTypeId, "aria-label": "Slider extent selector" }, classes: { root: selectClasses2.selectRoot }, children: [jsx$1("option", { value: "Value Min/Max", children: "Value Min/Max" }), jsx$1("option", { value: "Dtype Min/Max", children: "Dtype Min/Max" })] })] }), jsx$1(MenuItem, { dense: true, disableGutters: true, component: "button", onClick: onResetWindowUsingIQR, className: classes2.menuItemButton, "aria-label": "Click to use IQR for channel", children: "Reset window using IQR" }), jsx$1(MenuItem, { dense: true, disableGutters: true, component: "button", onClick: handleRemove, className: classes2.menuItemButton, "aria-label": "Click to remove channel", children: "Remove channel" })] });
265115
265204
  }
265116
- const useStyles$b = makeStyles()(() => ({
265205
+ const useStyles$c = makeStyles()(() => ({
265117
265206
  channelSlider: {
265118
265207
  marginTop: "7px"
265119
265208
  }
@@ -265123,7 +265212,7 @@ function ChannelSlider(props) {
265123
265212
  const rgbColor = toRgbUIString(colormapOn, color2, theme);
265124
265213
  const dtype = image2?.getDtype();
265125
265214
  const fullDomain = dtype ? DOMAINS[dtype] : [0, 0];
265126
- const { classes: classes2 } = useStyles$b();
265215
+ const { classes: classes2 } = useStyles$c();
265127
265216
  useEffect(() => {
265128
265217
  if (!window2 && !disabled && Array.isArray(minMaxDomain)) {
265129
265218
  setWindow2(minMaxDomain);
@@ -265138,7 +265227,7 @@ function ChannelSlider(props) {
265138
265227
  return `${labelPrefix} for ${rgbColor} colormap channel`;
265139
265228
  }, getAriaValueText: () => `Current colormap values: ${rgbColor}-${window2}`, min: min2, max: max2, step, orientation: "horizontal", className: classes2.channelSlider, style: { color: rgbColor }, disabled });
265140
265229
  }
265141
- const useStyles$a = makeStyles()(() => ({
265230
+ const useStyles$b = makeStyles()(() => ({
265142
265231
  visibilityCheckbox: {
265143
265232
  padding: "10px 0"
265144
265233
  }
@@ -265152,10 +265241,10 @@ function getCheckboxColor(colormapOn, color2, theme) {
265152
265241
  function ChannelVisibilityCheckbox(props) {
265153
265242
  const { color: color2, visible, setVisible, disabled, theme, colormapOn } = props;
265154
265243
  const rgbColor = getCheckboxColor(colormapOn, color2, theme);
265155
- const { classes: classes2 } = useStyles$a();
265244
+ const { classes: classes2 } = useStyles$b();
265156
265245
  return jsx$1(Checkbox$1, { onChange: (e3, v) => setVisible(v), checked: visible, disabled, className: classes2.visibilityCheckbox, style: { color: rgbColor, "&$checked": { color: rgbColor } }, slotProps: { input: { "aria-label": "Toggle channel visibility" } } });
265157
265246
  }
265158
- const useStyles$9 = makeStyles()(() => ({
265247
+ const useStyles$a = makeStyles()(() => ({
265159
265248
  oneLineChannelSelect: {
265160
265249
  width: "90%",
265161
265250
  marginLeft: "5%",
@@ -265163,17 +265252,23 @@ const useStyles$9 = makeStyles()(() => ({
265163
265252
  }
265164
265253
  }));
265165
265254
  function ChannelSelectionDropdown(props) {
265166
- const { featureIndex, targetC, setTargetC, setWindow: setWindow2, disabled } = props;
265167
- const { classes: classes2 } = useStyles$9();
265255
+ const { featureIndex, channelsSortOrder, targetC, setTargetC, setWindow: setWindow2, disabled } = props;
265256
+ const { classes: classes2 } = useStyles$a();
265168
265257
  const { classes: selectClasses2 } = useSelectStyles();
265169
265258
  function handleChange(event2) {
265170
265259
  setTargetC(event2.target.value === "" ? null : Number(event2.target.value));
265171
265260
  setWindow2(null);
265172
265261
  }
265173
- return Array.isArray(featureIndex) ? jsx$1(NativeSelect, { classes: { root: selectClasses2.selectRoot }, className: classes2.oneLineChannelSelect, value: targetC === null ? "" : targetC, onChange: handleChange, inputProps: { "aria-label": "Channel selector" }, children: featureIndex.map((channelName, channelIndex) => (
265174
- // eslint-disable-next-line react/no-array-index-key
265175
- jsx$1("option", { disabled, value: channelIndex, children: channelName }, `${channelName}-${channelIndex}`)
265176
- )) }) : null;
265262
+ const sortedOptions = useMemo$1(() => {
265263
+ if (!Array.isArray(featureIndex))
265264
+ return [];
265265
+ const options = featureIndex.map((channelName, channelIndex) => ({ channelName, channelIndex }));
265266
+ if (channelsSortOrder === "alphabetical") {
265267
+ return options.sort((a2, b2) => a2.channelName.localeCompare(b2.channelName, void 0, { numeric: true, sensitivity: "base" }));
265268
+ }
265269
+ return options;
265270
+ }, [featureIndex, channelsSortOrder]);
265271
+ return Array.isArray(featureIndex) ? jsx$1(NativeSelect, { classes: { root: selectClasses2.selectRoot }, className: classes2.oneLineChannelSelect, value: targetC === null ? "" : targetC, onChange: handleChange, inputProps: { "aria-label": "Channel selector" }, children: sortedOptions.map(({ channelName, channelIndex }) => jsx$1("option", { disabled, value: channelIndex, children: channelName }, `${channelName}-${channelIndex}`)) }) : null;
265177
265272
  }
265178
265273
  function ImageChannelController(props) {
265179
265274
  const {
@@ -265201,7 +265296,8 @@ function ImageChannelController(props) {
265201
265296
  spatialRenderingMode,
265202
265297
  numResolutions,
265203
265298
  spatialMaxResolution,
265204
- setSpatialMaxResolution
265299
+ setSpatialMaxResolution,
265300
+ channelsSortOrder
265205
265301
  } = props;
265206
265302
  const removeChannel = useRemoveImageChannelInMetaCoordinationScopes();
265207
265303
  const [showValueExtent, setShowValueExtent] = useState(true);
@@ -265238,7 +265334,7 @@ function ImageChannelController(props) {
265238
265334
  setWindow2(minMaxDomain);
265239
265335
  }
265240
265336
  }
265241
- return jsxs(Grid$1, { container: true, direction: "row", justifyContent: "space-between", children: [jsx$1(Grid$1, { size: 1, children: jsx$1(ChannelVisibilityCheckbox, { color: color2, setColor: setColor2, visible, setVisible, disabled: isLoading, theme, colormapOn }) }), jsx$1(Grid$1, { size: 1, children: jsx$1(ChannelColorPickerMenu, { color: color2, setColor: setColor2, visible, setVisible, disabled: isLoading, theme, isStaticColor: !colormapOn, palette: VIEWER_PALETTE }) }), jsx$1(Grid$1, { size: 6, children: jsx$1(ChannelSelectionDropdown, { featureIndex, targetC, setTargetC, setWindow: setWindow2, disabled: isLoading }) }), jsx$1(Grid$1, { size: 3, children: jsx$1(ChannelSlider, { image: image2, targetT, targetZ, targetC, window: window2, setWindow: setWindow2, disabled, color: color2, theme, colormapOn, showValueExtent, minMaxDomain }) }), jsx$1(Grid$1, { size: 1, children: jsx$1(ChannelOptions, { onRemove: onRemove2, showValueExtent, setShowValueExtent, onResetWindowUsingIQR: handleResetWindowUsingIQR, numResolutions, spatialMaxResolution, setSpatialMaxResolution }) })] });
265337
+ return jsxs(Grid$1, { container: true, direction: "row", justifyContent: "space-between", children: [jsx$1(Grid$1, { size: 1, children: jsx$1(ChannelVisibilityCheckbox, { color: color2, setColor: setColor2, visible, setVisible, disabled: isLoading, theme, colormapOn }) }), jsx$1(Grid$1, { size: 1, children: jsx$1(ChannelColorPickerMenu, { color: color2, setColor: setColor2, visible, setVisible, disabled: isLoading, theme, isStaticColor: !colormapOn, palette: VIEWER_PALETTE }) }), jsx$1(Grid$1, { size: 6, children: jsx$1(ChannelSelectionDropdown, { featureIndex, targetC, setTargetC, setWindow: setWindow2, disabled: isLoading, channelsSortOrder }) }), jsx$1(Grid$1, { size: 3, children: jsx$1(ChannelSlider, { image: image2, targetT, targetZ, targetC, window: window2, setWindow: setWindow2, disabled, color: color2, theme, colormapOn, showValueExtent, minMaxDomain }) }), jsx$1(Grid$1, { size: 1, children: jsx$1(ChannelOptions, { onRemove: onRemove2, showValueExtent, setShowValueExtent, onResetWindowUsingIQR: handleResetWindowUsingIQR, numResolutions, spatialMaxResolution, setSpatialMaxResolution }) })] });
265242
265338
  }
265243
265339
  function DimensionSlider(props) {
265244
265340
  const { boundingSlice, sliceValue, setSliceValue, label: label2 } = props;
@@ -265292,7 +265388,7 @@ function LoadingStatusIndicator(props) {
265292
265388
  }
265293
265389
  return jsxs(Grid$1, { container: true, px: 0.5, children: [jsx$1(Grid$1, { size: 6, alignItems: "start", children: jsx$1(Grid$1, { container: true, alignItems: "center", gap: 1, children: statusColor !== "success" ? jsx$1(StatusChip, { label: statusLabel, color: statusColor, size: "small" }) : null }) }), jsx$1(Grid$1, { size: 6, px: 1, my: 0.25, children: jsx$1(LoadingText, { bricksLoaded, currentRequestCount, totalBricksRequested, isLoading }) }), jsx$1(Grid$1, { size: 12, children: jsxs(Grid$1, { container: true, direction: "row", spacing: 2, alignItems: "center", wrap: "nowrap", children: [jsx$1(LoadingControlButton, { startIcon: jsx$1(Stop, {}), onClick: onStopLoading, disabled: noNewRequests, ariaLabel: "Stop loading data and render at highest resolution", children: "Stop Loading" }), jsx$1(LoadingControlButton, { startIcon: jsx$1(Replay, {}), onClick: onRestartLoading, disabled: !noNewRequests && !isLoading, ariaLabel: "Restart data loading", children: "Restart Loading" })] }) }), jsx$1(Grid$1, { size: 12, visibility: isLoading ? "visible" : "hidden", mt: 1, mb: 0.5, children: jsx$1(LinearProgress, { variant: "determinate", value: percentage }) })] });
265294
265390
  }
265295
- const useStyles$8 = makeStyles()(() => ({
265391
+ const useStyles$9 = makeStyles()(() => ({
265296
265392
  imageLayerButton: {
265297
265393
  borderStyle: "dashed",
265298
265394
  marginTop: "10px",
@@ -265337,7 +265433,7 @@ const lodSliderRangeConfig = {
265337
265433
  step: 1
265338
265434
  };
265339
265435
  function ImageLayerEllipsisMenu(props) {
265340
- const { colormap, setColormap, photometricInterpretation, setPhotometricInterpretation, spatialTargetResolution, setSpatialTargetResolution, spatialLodFactor, setSpatialLodFactor, volumetricRenderingAlgorithm, setVolumetricRenderingAlgorithm, spatialLayerTransparentColor, setSpatialLayerTransparentColor, spatialRenderingMode, image: image2, channelScopes, tooltipsVisible, setTooltipsVisible, channelLabelsVisible, setChannelLabelsVisible, channelLabelsOrientation, setChannelLabelsOrientation, channelLabelSize, setChannelLabelSize } = props;
265436
+ const { colormap, setColormap, photometricInterpretation, setPhotometricInterpretation, spatialTargetResolution, setSpatialTargetResolution, spatialLodFactor, setSpatialLodFactor, volumetricRenderingAlgorithm, setVolumetricRenderingAlgorithm, spatialLayerTransparentColor, setSpatialLayerTransparentColor, spatialRenderingMode, image: image2, channelScopes, tooltipsVisible, setTooltipsVisible, channelLabelsVisible, setChannelLabelsVisible, channelLabelsOrientation, setChannelLabelsOrientation, channelLabelSize, setChannelLabelSize, channelsSortOrder, setChannelsSortOrder } = props;
265341
265437
  const [open2, setOpen] = useState(false);
265342
265438
  const { classes: selectClasses2 } = useSelectStyles();
265343
265439
  const { classes: menuClasses } = useEllipsisMenuStyles();
@@ -265363,6 +265459,9 @@ function ImageLayerEllipsisMenu(props) {
265363
265459
  function handleChannelLabelsOrientationChange(event2) {
265364
265460
  setChannelLabelsOrientation(event2.target.value);
265365
265461
  }
265462
+ function handleChannelsSortOrderChange(event2) {
265463
+ setChannelsSortOrder(event2.target.value);
265464
+ }
265366
265465
  function handleLodFactorChange(event2, value2) {
265367
265466
  setLocalLodSliderValue(value2);
265368
265467
  }
@@ -265384,11 +265483,12 @@ function ImageLayerEllipsisMenu(props) {
265384
265483
  const channelLabelsOrientationId = $bdb11010cef70236$export$f680877a34711e37();
265385
265484
  const channelLabelSizeId = $bdb11010cef70236$export$f680877a34711e37();
265386
265485
  const lodFactorId = $bdb11010cef70236$export$f680877a34711e37();
265486
+ const channelsSortOrderId = $bdb11010cef70236$export$f680877a34711e37();
265387
265487
  return jsxs(PopperMenu, { open: open2, setOpen, buttonIcon: jsx$1(MoreVertIcon, {}), buttonClassName: menuClasses.imageLayerMenuButton, containerClassName: menuClasses.imageLayerPopperContainer, withPaper: true, "aria-label": "Open image layer options menu", children: [is3dMode && jsxs(MenuItem, { dense: true, disableGutters: true, className: menuClasses.menuItem, children: [jsx$1("label", { id: `${lodFactorId}-label`, className: menuClasses.imageLayerMenuLabel, htmlFor: lodFactorId, children: "LOD Factor: " }), jsx$1(Slider, { disabled: !is3dMode, value: localLodSliderValue, onChange: handleLodFactorChange, onChangeCommitted: handleLodFactorChangeCommitted, className: menuClasses.menuItemSlider, orientation: "horizontal", id: lodFactorId, "aria-label": "Level of detail factor slider", valueLabelDisplay: "off", ...lodSliderRangeConfig }), jsx$1(Box, { px: 1 }), jsx$1(Input$1, { value: localLodSliderValue, onChange: handleLodFactorInputChange, onBlur: handleLodFactorInputChange, "aria-labelledby": `${lodFactorId}-label`, size: "small", sx: { width: "6ch", flexShrink: 0, fontVariantNumeric: "tabular-nums" }, endAdornment: jsx$1(InputAdornment$1, { position: "end", variant: "standard", sx: { "& > p": { marginBottom: 0 } }, children: "%" }), inputProps: {
265388
265488
  ...lodSliderRangeConfig,
265389
265489
  inputMode: "numeric",
265390
265490
  pattern: "[0-9]*"
265391
- } })] }), jsxs(MenuItem, { dense: true, disableGutters: true, classes: menuClasses.menuItem, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: colormapId, children: "Colormap: " }), jsxs(NativeSelect, { disabled: photometricInterpretation === "RGB", onChange: handleColormapChange, value: colormap === null ? "" : colormap, inputProps: { id: colormapId, "aria-label": "Colormap selector" }, classes: { root: selectClasses2.selectRoot }, children: [jsx$1("option", { "aria-label": "None", value: "", children: "None" }), COLORMAP_OPTIONS.map((name2) => jsx$1("option", { value: name2, children: name2 }, name2))] })] }), jsxs(MenuItem, { dense: true, disableGutters: true, classes: menuClasses.menuItem, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: interpretationId, children: "Photometric Interpretation: " }), jsxs(NativeSelect, { onChange: handleInterpretationChange, value: photometricInterpretation, inputProps: { id: interpretationId, "aria-label": "Photometric interpretation selector" }, classes: { root: selectClasses2.selectRoot }, children: [jsx$1("option", { "aria-label": "RGB", value: "RGB", children: "RGB" }), channelScopes.length > 0 ? jsx$1("option", { "aria-label": "BlackIsZero", value: "BlackIsZero", children: "BlackIsZero" }) : null] })] }), jsxs(MenuItem, { dense: true, disableGutters: true, classes: menuClasses.menuItem, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: transparentId, children: "Zero Transparent: " }), jsx$1(Checkbox$1, { color: "primary", checked: spatialLayerTransparentColor !== null, onChange: (e3, v) => setSpatialLayerTransparentColor(v ? [0, 0, 0] : null), slotProps: { input: { id: transparentId, "aria-label": "Render zero-value pixels as transparent" } } })] }), jsxs(MenuItem, { dense: true, disableGutters: true, classes: menuClasses.menuItem, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: volumetricId, children: "Volumetric Rendering: " }), jsxs(NativeSelect, { onChange: handleVolumetricChange, value: volumetricRenderingAlgorithm, inputProps: { id: volumetricId, "aria-label": "Volumetric rendering algorithm selector" }, classes: { root: selectClasses2.selectRoot }, disabled: !is3dMode, children: [jsx$1("option", { "aria-label": "Additive", value: "additive", children: "Additive" }), jsx$1("option", { "aria-label": "Maximum Intensity Projection", value: "maximumIntensityProjection", children: "Maximum Intensity Projection" })] })] }), jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: resolutionId, children: "Volume Resolution: " }), jsxs(NativeSelect, { disabled: !is3dMode || !isMultiResolution, onChange: handleResolutionChange, value: spatialTargetResolution === null ? "auto" : spatialTargetResolution, inputProps: { id: resolutionId, "aria-label": "Volumetric resolution selector" }, classes: { root: selectClasses2.selectRoot }, children: [jsx$1("option", { value: "auto", children: "Auto" }), Array.isArray(multiResolutionStats) ? multiResolutionStats.map((stats, resolution) => stats.canLoad ? jsx$1("option", { value: resolution, children: `3D: ${resolution}x Downsampled, ~${formatBytes(stats.totalBytes)} per channel, (${stats.height}, ${stats.width}, ${stats.depthDownsampled})` }, `(${stats.height}, ${stats.width}, ${stats.depthDownsampled})`) : null) : null] })] }), jsxs(MenuItem, { dense: true, disableGutters: true, classes: menuClasses.menuItem, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: tooltipsVisibleId, children: "Tooltips Visible: " }), jsx$1(Checkbox$1, { color: "primary", checked: tooltipsVisible, onChange: (e3, v) => setTooltipsVisible(v), slotProps: { input: { id: tooltipsVisibleId, "aria-label": "Render pixel value tooltips" } } })] }), jsxs(MenuItem, { dense: true, disableGutters: true, classes: menuClasses.menuItem, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: channelLabelsVisibleId, children: "Channel Labels Visible: " }), jsx$1(Checkbox$1, { disabled: photometricInterpretation === "RGB", color: "primary", checked: channelLabelsVisible, onChange: (e3, v) => setChannelLabelsVisible(v), slotProps: { input: { id: channelLabelsVisibleId, "aria-label": "Render channel labels" } } })] }), jsxs(MenuItem, { dense: true, disableGutters: true, classes: menuClasses.menuItem, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: channelLabelsOrientationId, children: "Channel Labels Orientation: " }), jsxs(NativeSelect, { disabled: photometricInterpretation === "RGB", onChange: handleChannelLabelsOrientationChange, value: channelLabelsOrientation, inputProps: { id: channelLabelsOrientationId, "aria-label": "Channel labels orientation selector" }, classes: { root: selectClasses2.selectRoot }, children: [jsx$1("option", { "aria-label": "Vertical", value: "vertical", children: "Vertical" }), jsx$1("option", { "aria-label": "Horizontal", value: "horizontal", children: "Horizontal" })] })] }), jsxs(MenuItem, { dense: true, disableGutters: true, classes: menuClasses.menuItem, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: channelLabelSizeId, children: "Channel Labels Size: " }), jsx$1(Slider, { disabled: photometricInterpretation === "RGB", value: channelLabelSize, min: 8, max: 36, step: 1, onChange: (e3, v) => setChannelLabelSize(v), className: menuClasses.menuItemSlider, orientation: "horizontal", id: channelLabelSizeId, "aria-label": "Channel labels text size slider" })] })] });
265491
+ } })] }), jsxs(MenuItem, { dense: true, disableGutters: true, classes: menuClasses.menuItem, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: colormapId, children: "Colormap: " }), jsxs(NativeSelect, { disabled: photometricInterpretation === "RGB", onChange: handleColormapChange, value: colormap === null ? "" : colormap, inputProps: { id: colormapId, "aria-label": "Colormap selector" }, classes: { root: selectClasses2.selectRoot }, children: [jsx$1("option", { "aria-label": "None", value: "", children: "None" }), COLORMAP_OPTIONS.map((name2) => jsx$1("option", { value: name2, children: name2 }, name2))] })] }), jsxs(MenuItem, { dense: true, disableGutters: true, classes: menuClasses.menuItem, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: interpretationId, children: "Photometric Interpretation: " }), jsxs(NativeSelect, { onChange: handleInterpretationChange, value: photometricInterpretation, inputProps: { id: interpretationId, "aria-label": "Photometric interpretation selector" }, classes: { root: selectClasses2.selectRoot }, children: [jsx$1("option", { "aria-label": "RGB", value: "RGB", children: "RGB" }), channelScopes.length > 0 ? jsx$1("option", { "aria-label": "BlackIsZero", value: "BlackIsZero", children: "BlackIsZero" }) : null] })] }), jsxs(MenuItem, { dense: true, disableGutters: true, classes: menuClasses.menuItem, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: transparentId, children: "Zero Transparent: " }), jsx$1(Checkbox$1, { color: "primary", checked: spatialLayerTransparentColor !== null, onChange: (e3, v) => setSpatialLayerTransparentColor(v ? [0, 0, 0] : null), slotProps: { input: { id: transparentId, "aria-label": "Render zero-value pixels as transparent" } } })] }), jsxs(MenuItem, { dense: true, disableGutters: true, classes: menuClasses.menuItem, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: volumetricId, children: "Volumetric Rendering: " }), jsxs(NativeSelect, { onChange: handleVolumetricChange, value: volumetricRenderingAlgorithm, inputProps: { id: volumetricId, "aria-label": "Volumetric rendering algorithm selector" }, classes: { root: selectClasses2.selectRoot }, disabled: !is3dMode, children: [jsx$1("option", { "aria-label": "Additive", value: "additive", children: "Additive" }), jsx$1("option", { "aria-label": "Maximum Intensity Projection", value: "maximumIntensityProjection", children: "Maximum Intensity Projection" })] })] }), jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: resolutionId, children: "Volume Resolution: " }), jsxs(NativeSelect, { disabled: !is3dMode || !isMultiResolution, onChange: handleResolutionChange, value: spatialTargetResolution === null ? "auto" : spatialTargetResolution, inputProps: { id: resolutionId, "aria-label": "Volumetric resolution selector" }, classes: { root: selectClasses2.selectRoot }, children: [jsx$1("option", { value: "auto", children: "Auto" }), Array.isArray(multiResolutionStats) ? multiResolutionStats.map((stats, resolution) => stats.canLoad ? jsx$1("option", { value: resolution, children: `3D: ${resolution}x Downsampled, ~${formatBytes(stats.totalBytes)} per channel, (${stats.height}, ${stats.width}, ${stats.depthDownsampled})` }, `(${stats.height}, ${stats.width}, ${stats.depthDownsampled})`) : null) : null] })] }), jsxs(MenuItem, { dense: true, disableGutters: true, classes: menuClasses.menuItem, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: tooltipsVisibleId, children: "Tooltips Visible: " }), jsx$1(Checkbox$1, { color: "primary", checked: tooltipsVisible, onChange: (e3, v) => setTooltipsVisible(v), slotProps: { input: { id: tooltipsVisibleId, "aria-label": "Render pixel value tooltips" } } })] }), jsxs(MenuItem, { dense: true, disableGutters: true, classes: menuClasses.menuItem, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: channelLabelsVisibleId, children: "Channel Labels Visible: " }), jsx$1(Checkbox$1, { disabled: photometricInterpretation === "RGB", color: "primary", checked: channelLabelsVisible, onChange: (e3, v) => setChannelLabelsVisible(v), slotProps: { input: { id: channelLabelsVisibleId, "aria-label": "Render channel labels" } } })] }), jsxs(MenuItem, { dense: true, disableGutters: true, classes: menuClasses.menuItem, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: channelLabelsOrientationId, children: "Channel Labels Orientation: " }), jsxs(NativeSelect, { disabled: photometricInterpretation === "RGB", onChange: handleChannelLabelsOrientationChange, value: channelLabelsOrientation, inputProps: { id: channelLabelsOrientationId, "aria-label": "Channel labels orientation selector" }, classes: { root: selectClasses2.selectRoot }, children: [jsx$1("option", { "aria-label": "Vertical", value: "vertical", children: "Vertical" }), jsx$1("option", { "aria-label": "Horizontal", value: "horizontal", children: "Horizontal" })] })] }), jsxs(MenuItem, { dense: true, disableGutters: true, classes: menuClasses.menuItem, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: channelLabelSizeId, children: "Channel Labels Size: " }), jsx$1(Slider, { disabled: photometricInterpretation === "RGB", value: channelLabelSize, min: 8, max: 36, step: 1, onChange: (e3, v) => setChannelLabelSize(v), className: menuClasses.menuItemSlider, orientation: "horizontal", id: channelLabelSizeId, "aria-label": "Channel labels text size slider" })] }), jsxs(MenuItem, { dense: true, disableGutters: true, classes: menuClasses.menuItem, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: channelsSortOrderId, children: "Channel Ordering: " }), jsxs(NativeSelect, { onChange: handleChannelsSortOrderChange, value: channelsSortOrder, inputProps: { id: channelsSortOrderId, "aria-label": "Channel sort order selector" }, classes: { root: selectClasses2.selectRoot }, children: [jsx$1("option", { "aria-label": "Original", value: "original", children: "Original" }), jsx$1("option", { "aria-label": "Alphabetical", value: "alphabetical", children: "Alphabetical" })] })] })] });
265392
265492
  }
265393
265493
  function ImageLayerController(props) {
265394
265494
  const {
@@ -265414,8 +265514,8 @@ function ImageLayerController(props) {
265414
265514
  volumeStillRef = null
265415
265515
  } = props;
265416
265516
  const [open2, setOpen] = useState(true);
265417
- const { spatialLayerVisible: visible, spatialLayerOpacity: opacity2, spatialLayerColormap: colormap, photometricInterpretation, spatialTargetResolution, spatialLodFactor, volumetricRenderingAlgorithm, spatialLayerTransparentColor, spatialSliceX, spatialSliceY, spatialSliceZ, tooltipsVisible, spatialChannelLabelsVisible: channelLabelsVisible, spatialChannelLabelsOrientation: channelLabelsOrientation, spatialChannelLabelSize: channelLabelSize } = layerCoordination;
265418
- const { setSpatialLayerVisible: setVisible, setSpatialLayerOpacity: setOpacity2, setSpatialLayerColormap: setColormap, setPhotometricInterpretation, setSpatialTargetResolution, setSpatialLodFactor, setVolumetricRenderingAlgorithm, setSpatialLayerTransparentColor, setSpatialSliceX, setSpatialSliceY, setSpatialSliceZ, setTooltipsVisible, setSpatialChannelLabelsVisible: setChannelLabelsVisible, setSpatialChannelLabelsOrientation: setChannelLabelsOrientation, setSpatialChannelLabelSize: setChannelLabelSize } = setLayerCoordination;
265517
+ const { spatialLayerVisible: visible, spatialLayerOpacity: opacity2, spatialLayerColormap: colormap, photometricInterpretation, spatialTargetResolution, spatialLodFactor, volumetricRenderingAlgorithm, spatialLayerTransparentColor, spatialSliceX, spatialSliceY, spatialSliceZ, tooltipsVisible, spatialChannelLabelsVisible: channelLabelsVisible, spatialChannelLabelsOrientation: channelLabelsOrientation, spatialChannelLabelSize: channelLabelSize, spatialChannelsSortOrder: channelsSortOrder } = layerCoordination;
265518
+ const { setSpatialLayerVisible: setVisible, setSpatialLayerOpacity: setOpacity2, setSpatialLayerColormap: setColormap, setPhotometricInterpretation, setSpatialTargetResolution, setSpatialLodFactor, setVolumetricRenderingAlgorithm, setSpatialLayerTransparentColor, setSpatialSliceX, setSpatialSliceY, setSpatialSliceZ, setTooltipsVisible, setSpatialChannelLabelsVisible: setChannelLabelsVisible, setSpatialChannelLabelsOrientation: setChannelLabelsOrientation, setSpatialChannelLabelSize: setChannelLabelSize, setSpatialChannelsSortOrder: setChannelsSortOrder } = setLayerCoordination;
265419
265519
  const addChannel = useAddImageChannelInMetaCoordinationScopes();
265420
265520
  const visibleSetting = typeof visible === "boolean" ? visible : true;
265421
265521
  const Visibility = useMemo$1(() => visibleSetting ? VisibilityIcon : VisibilityOffIcon, [visibleSetting]);
@@ -265431,19 +265531,19 @@ function ImageLayerController(props) {
265431
265531
  }, [visible, setVisible]);
265432
265532
  const handleOpacityChange = useCallback((e3, v) => setOpacity2(v), [setOpacity2]);
265433
265533
  const handleOpenChange = useCallback(() => setOpen((prev2) => !prev2), []);
265434
- const { classes: classes2 } = useStyles$8();
265534
+ const { classes: classes2 } = useStyles$9();
265435
265535
  const { classes: menuClasses } = useEllipsisMenuStyles();
265436
265536
  const { classes: controllerSectionClasses } = useControllerSectionStyles();
265437
265537
  const isMultiChannel = photometricInterpretation !== "RGB";
265438
- return jsx$1(Grid$1, { className: controllerSectionClasses.layerControllerGrid, children: jsxs(Paper, { elevation: 4, className: controllerSectionClasses.layerControllerRoot, children: [jsxs(Grid$1, { container: true, direction: "row", justifyContent: "space-between", children: [jsx$1(Grid$1, { size: 1, children: jsx$1(Button, { className: menuClasses.imageLayerVisibleButton, onClick: handleVisibleChange, "aria-label": "Toggle layer visibility", children: jsx$1(Visibility, {}) }) }), jsx$1(Grid$1, { size: 1 }), jsx$1(Grid$1, { size: 6, children: jsx$1(Typography, { className: menuClasses.imageLayerName, children: label2 }) }), jsx$1(Grid$1, { size: 2, children: jsx$1(Slider, { value: opacity2, min: 0, max: 1, step: 1e-3, onChange: handleOpacityChange, className: menuClasses.imageLayerOpacitySlider, orientation: "horizontal", "aria-label": `Adjust opacity for layer ${label2}` }) }), jsx$1(Grid$1, { size: 1, children: jsx$1(ImageLayerEllipsisMenu, { colormap, setColormap, photometricInterpretation, setPhotometricInterpretation, spatialTargetResolution, setSpatialTargetResolution, spatialLodFactor, setSpatialLodFactor, volumetricRenderingAlgorithm, setVolumetricRenderingAlgorithm, spatialLayerTransparentColor, setSpatialLayerTransparentColor, spatialRenderingMode, image: image2, channelScopes, tooltipsVisible, setTooltipsVisible, channelLabelsVisible, setChannelLabelsVisible, channelLabelsOrientation, setChannelLabelsOrientation, channelLabelSize, setChannelLabelSize }) }), jsxs(Grid$1, { size: 1, container: true, direction: "row", children: [jsx$1(ImageIcon, { className: classes2.layerTypeImageIcon }), isMultiChannel ? jsx$1(Button, { onClick: handleOpenChange, className: classes2.channelExpansionButton, "aria-label": "Expand or collapse channel controls", children: open2 ? jsx$1(ExpandLess, {}) : jsx$1(ExpandMoreIcon, {}) }) : null] })] }), isMultiChannel && open2 ? jsxs(Grid$1, { container: true, direction: "column", justifyContent: "space-between", className: classes2.imageChannelControllerGrid, children: [channelScopes.map((cScope) => {
265538
+ return jsx$1(Grid$1, { className: controllerSectionClasses.layerControllerGrid, children: jsxs(Paper, { elevation: 4, className: controllerSectionClasses.layerControllerRoot, children: [jsxs(Grid$1, { container: true, direction: "row", justifyContent: "space-between", children: [jsx$1(Grid$1, { size: 1, children: jsx$1(Button, { className: menuClasses.imageLayerVisibleButton, onClick: handleVisibleChange, "aria-label": "Toggle layer visibility", children: jsx$1(Visibility, {}) }) }), jsx$1(Grid$1, { size: 1 }), jsx$1(Grid$1, { size: 6, children: jsx$1(Typography, { className: menuClasses.imageLayerName, children: label2 }) }), jsx$1(Grid$1, { size: 2, children: jsx$1(Slider, { value: opacity2, min: 0, max: 1, step: 1e-3, onChange: handleOpacityChange, className: menuClasses.imageLayerOpacitySlider, orientation: "horizontal", "aria-label": `Adjust opacity for layer ${label2}` }) }), jsx$1(Grid$1, { size: 1, children: jsx$1(ImageLayerEllipsisMenu, { colormap, setColormap, photometricInterpretation, setPhotometricInterpretation, spatialTargetResolution, setSpatialTargetResolution, spatialLodFactor, setSpatialLodFactor, volumetricRenderingAlgorithm, setVolumetricRenderingAlgorithm, spatialLayerTransparentColor, setSpatialLayerTransparentColor, spatialRenderingMode, image: image2, channelScopes, tooltipsVisible, setTooltipsVisible, channelLabelsVisible, setChannelLabelsVisible, channelLabelsOrientation, setChannelLabelsOrientation, channelLabelSize, setChannelLabelSize, channelsSortOrder, setChannelsSortOrder }) }), jsxs(Grid$1, { size: 1, container: true, direction: "row", children: [jsx$1(ImageIcon, { className: classes2.layerTypeImageIcon }), isMultiChannel ? jsx$1(Button, { onClick: handleOpenChange, className: classes2.channelExpansionButton, "aria-label": "Expand or collapse channel controls", children: open2 ? jsx$1(ExpandLess, {}) : jsx$1(ExpandMoreIcon, {}) }) : null] })] }), isMultiChannel && open2 ? jsxs(Grid$1, { container: true, direction: "column", justifyContent: "space-between", className: classes2.imageChannelControllerGrid, children: [channelScopes.map((cScope) => {
265439
265539
  const { spatialTargetC, spatialChannelVisible, spatialChannelOpacity, spatialChannelColor, spatialChannelWindow, spatialMaxResolution } = channelCoordination[cScope];
265440
265540
  const { setSpatialTargetC, setSpatialChannelVisible, setSpatialChannelOpacity, setSpatialChannelColor, setSpatialChannelWindow, setSpatialMaxResolution } = setChannelCoordination[cScope];
265441
265541
  const channelIndex = image2?.getChannelIndex(spatialTargetC);
265442
265542
  const numResolutions = image2?.getNumResolutions();
265443
- return jsx$1(ImageChannelController, { theme, coordinationScopesRaw, layerScope, channelScope: cScope, targetT, targetZ, targetC: channelIndex, setTargetC: setSpatialTargetC, visible: spatialChannelVisible, setVisible: setSpatialChannelVisible, opacity: spatialChannelOpacity, setOpacity: setSpatialChannelOpacity, color: spatialChannelColor, setColor: setSpatialChannelColor, window: spatialChannelWindow, setWindow: setSpatialChannelWindow, colormapOn: colormap !== null, featureIndex, image: image2, spatialRenderingMode, numResolutions, spatialMaxResolution, setSpatialMaxResolution }, cScope);
265543
+ return jsx$1(ImageChannelController, { theme, coordinationScopesRaw, layerScope, channelScope: cScope, targetT, targetZ, targetC: channelIndex, setTargetC: setSpatialTargetC, visible: spatialChannelVisible, setVisible: setSpatialChannelVisible, opacity: spatialChannelOpacity, setOpacity: setSpatialChannelOpacity, color: spatialChannelColor, setColor: setSpatialChannelColor, window: spatialChannelWindow, setWindow: setSpatialChannelWindow, colormapOn: colormap !== null, featureIndex, image: image2, channelsSortOrder, spatialRenderingMode, numResolutions, spatialMaxResolution, setSpatialMaxResolution }, cScope);
265444
265544
  }), jsx$1(Button, { disabled: channelScopes.length === MAX_CHANNELS$1 || channelScopes.length === imageNumChannels, onClick: handleChannelAdd, fullWidth: true, variant: "outlined", className: classes2.imageLayerButton, startIcon: jsx$1(AddIcon, {}), size: "small", "aria-label": "Add a channel to this layer", children: "Add Channel" })] }) : null, is3dMode && image2 ? jsxs(Grid$1, { container: true, direction: "column", justifyContent: "space-between", className: classes2.imageChannelControllerGrid, children: [jsx$1(Typography, { className: classes2.clippingPanesLabel, children: "Clipping planes:" }), jsx$1(Grid$1, { size: 12, className: classes2.clippingSliders, children: jsx$1(ClippingSliders, { image: image2, spatialSliceX, spatialSliceY, spatialSliceZ, setSpatialSliceX, setSpatialSliceY, setSpatialSliceZ }) }), jsx$1(LoadingStatusIndicator, { loadingProgress: volumeLoadingProgress, onStopLoading: onStopVolumeLoading, onRestartLoading: onRestartVolumeLoading, stillRef: volumeStillRef, spatialRenderingMode })] }) : null] }) });
265445
265545
  }
265446
- const useStyles$7 = makeStyles()((theme) => ({
265546
+ const useStyles$8 = makeStyles()((theme) => ({
265447
265547
  dimensionsIcon: {
265448
265548
  height: "40px",
265449
265549
  width: "40px",
@@ -265467,7 +265567,7 @@ const useStyles$7 = makeStyles()((theme) => ({
265467
265567
  function GlobalDimensionSlider(props) {
265468
265568
  const { label: label2, targetValue, setTargetValue, min: min2 = 0, max: max2 = 0, spatialRenderingMode = null, setSpatialRenderingMode = null } = props;
265469
265569
  const { classes: lcClasses } = useControllerSectionStyles();
265470
- const { classes: classes2 } = useStyles$7();
265570
+ const { classes: classes2 } = useStyles$8();
265471
265571
  const isForZ = spatialRenderingMode !== null;
265472
265572
  const is3dMode = spatialRenderingMode === "3D";
265473
265573
  function handleRenderingModeChange(event2) {
@@ -265478,20 +265578,115 @@ function GlobalDimensionSlider(props) {
265478
265578
  }
265479
265579
  return jsx$1(Grid$1, { className: lcClasses.layerControllerGrid, children: jsx$1(Paper, { elevation: 4, className: lcClasses.layerControllerRoot, children: jsxs(Grid$1, { container: true, direction: "row", justifyContent: "space-between", children: [jsx$1(Grid$1, { size: 1, children: jsx$1(SvgDimensions, { className: classes2.dimensionsIcon }) }), jsx$1(Grid$1, { size: 1, children: jsx$1(Typography, { className: classes2.dimensionLabel, children: label2 }) }), jsx$1(Grid$1, { size: 8, children: !is3dMode ? jsx$1(Slider, { value: targetValue, min: min2, max: max2, step: 1, onChange: (e3, v) => setTargetValue(v), className: classes2.dimensionSlider, valueLabelDisplay: "auto", orientation: "horizontal", disabled: spatialRenderingMode === "3D", "aria-label": `${label2}-slice slider` }) : null }), jsx$1(Grid$1, { size: 2, children: isForZ ? jsx$1(FormGroup, { row: true, className: classes2.switchFormGroup, children: jsx$1(FormControlLabel$1, { control: jsx$1(Switch, { color: "primary", checked: spatialRenderingMode === "3D", onChange: handleRenderingModeChange, name: "is3dMode" }), label: "3D" }) }) : null })] }) }) });
265480
265580
  }
265581
+ const useStyles$7 = makeStyles()(() => ({
265582
+ layerTypeCentroidSegmentationIcon: {
265583
+ height: "100%",
265584
+ marginLeft: "1px",
265585
+ fill: "currentColor",
265586
+ fontSize: "24px",
265587
+ width: "50%",
265588
+ maxWidth: "24px"
265589
+ },
265590
+ divider: {
265591
+ width: "1px",
265592
+ height: "20px",
265593
+ background: "currentColor",
265594
+ flexShrink: 0,
265595
+ margin: "0 10px"
265596
+ },
265597
+ inlineLabel: {
265598
+ margin: "0px !important",
265599
+ lineHeight: 1,
265600
+ alignSelf: "center"
265601
+ },
265602
+ iconCell: {
265603
+ display: "flex",
265604
+ alignItems: "center",
265605
+ justifyContent: "center"
265606
+ }
265607
+ }));
265608
+ function CombinedEllipsisMenu(props) {
265609
+ const { obsType, featureType, featureValueType, filled, setFilled, strokeWidth, setStrokeWidth, obsColorEncoding, setObsColorEncoding, featureValueColormap, setFeatureValueColormap, featureValueColormapRange, setFeatureValueColormapRange, tooltipsVisible, setTooltipsVisible, tooltipCrosshairsVisible, setTooltipCrosshairsVisible, legendVisible, setLegendVisible, setPointObsColorEncoding, setPointFeatureValueColormap, setPointFeatureValueColormapRange, onColorEncodingChange } = props;
265610
+ const [open2, setOpen] = useState(false);
265611
+ const { classes: selectClasses2 } = useSelectStyles();
265612
+ const { classes: menuClasses } = useEllipsisMenuStyles();
265613
+ const filledId = $bdb11010cef70236$export$f680877a34711e37();
265614
+ const strokeWidthId = $bdb11010cef70236$export$f680877a34711e37();
265615
+ const colorEncodingId = $bdb11010cef70236$export$f680877a34711e37();
265616
+ const colormapId = $bdb11010cef70236$export$f680877a34711e37();
265617
+ const colormapRangeId = $bdb11010cef70236$export$f680877a34711e37();
265618
+ const tooltipsId = $bdb11010cef70236$export$f680877a34711e37();
265619
+ const crosshairsId = $bdb11010cef70236$export$f680877a34711e37();
265620
+ const legendId = $bdb11010cef70236$export$f680877a34711e37();
265621
+ return jsxs(PopperMenu, { open: open2, setOpen, buttonIcon: jsx$1(MoreVertIcon, {}), buttonClassName: menuClasses.imageLayerMenuButton, containerClassName: menuClasses.imageLayerPopperContainer, withPaper: true, getAriaLabel: () => "Open combined centroid and segmentation layer options menu", children: [jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: filledId, children: "Filled: " }), jsx$1(Checkbox$1, { color: "primary", className: menuClasses.menuItemCheckbox, checked: filled, onChange: (e3, v) => setFilled(v), slotProps: { input: { id: filledId } } })] }), jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: strokeWidthId, children: "Stroke width:" }), jsx$1(Slider, { disabled: filled, value: strokeWidth, min: 0.01, max: 5, step: 0.01, onChange: (e3, v) => setStrokeWidth(v), className: menuClasses.menuItemSlider, orientation: "horizontal", id: strokeWidthId, getAriaLabel: () => "Stroke width" })] }), jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: colorEncodingId, children: "Color encoding: " }), jsxs(NativeSelect, { onChange: onColorEncodingChange, value: obsColorEncoding, inputProps: { id: colorEncodingId }, classes: { root: selectClasses2.selectRoot }, children: [jsx$1("option", { value: "spatialChannelColor", children: "Static Color" }), jsx$1("option", { value: "geneSelection", children: "Feature Value" }), jsx$1("option", { value: "cellSetSelection", children: "Set Selection" })] })] }), jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: colormapId, children: "Colormap: " }), jsx$1(NativeSelect, { onChange: (e3) => {
265622
+ setFeatureValueColormap(e3.target.value);
265623
+ setPointFeatureValueColormap?.(e3.target.value);
265624
+ }, value: featureValueColormap, inputProps: { id: colormapId }, classes: { root: selectClasses2.selectRoot }, children: GLSL_COLORMAPS.map((cmap) => jsx$1("option", { value: cmap, children: cmap }, cmap)) })] }), jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: colormapRangeId, children: "Colormap range: " }), jsx$1(Slider, { disabled: obsColorEncoding !== "geneSelection", value: featureValueColormapRange, min: 0, max: 1, step: 0.01, onChange: (e3, v) => {
265625
+ setFeatureValueColormapRange(v);
265626
+ setPointFeatureValueColormapRange?.(v);
265627
+ }, className: menuClasses.menuItemSlider, orientation: "horizontal", id: colormapRangeId, getAriaLabel: (i2) => i2 === 0 ? "Low colormap range" : "High colormap range" })] }), jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: tooltipsId, children: "Tooltips visible: " }), jsx$1(Checkbox$1, { color: "primary", className: menuClasses.menuItemCheckbox, checked: tooltipsVisible, onChange: (e3, v) => setTooltipsVisible(v), slotProps: { input: { id: tooltipsId } } })] }), jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: crosshairsId, children: "Crosshairs visible: " }), jsx$1(Checkbox$1, { color: "primary", className: menuClasses.menuItemCheckbox, checked: tooltipCrosshairsVisible, onChange: (e3, v) => setTooltipCrosshairsVisible(v), slotProps: { input: { id: crosshairsId } } })] }), jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsx$1("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: legendId, children: "Legend visible: " }), jsx$1(Checkbox$1, { color: "primary", className: menuClasses.menuItemCheckbox, checked: legendVisible, onChange: (e3, v) => setLegendVisible(v), slotProps: { input: { id: legendId } } })] })] });
265628
+ }
265629
+ function SegmentationCentroidsController(props) {
265630
+ const { theme, segScope, segLayerCoordination, segChannelScopesByLayer, segChannelCoordination, pointScope, pointLayerCoordination } = props;
265631
+ const { classes: classes2 } = useStyles$7();
265632
+ const { classes: lcClasses } = useControllerSectionStyles();
265633
+ const { classes: menuClasses } = useEllipsisMenuStyles();
265634
+ const channelScopes = segChannelScopesByLayer?.[segScope] ?? [];
265635
+ const primaryChanScope = channelScopes[0];
265636
+ const chanValues = segChannelCoordination[0]?.[segScope]?.[primaryChanScope] ?? {};
265637
+ const chanSetters = segChannelCoordination[1]?.[segScope]?.[primaryChanScope] ?? {};
265638
+ const { obsType, featureType, featureValueType, spatialChannelVisible: segVisible, spatialChannelOpacity: segOpacity, spatialChannelColor: segColor, spatialSegmentationFilled: filled, spatialSegmentationStrokeWidth: strokeWidth, obsColorEncoding, featureSelection, featureValueColormap, featureValueColormapRange, tooltipsVisible, tooltipCrosshairsVisible, legendVisible } = chanValues;
265639
+ const { setSpatialChannelVisible: setSegVisible, setSpatialChannelOpacity: setSegOpacity, setSpatialChannelColor: setSegColor, setSpatialSegmentationFilled: setFilled, setSpatialSegmentationStrokeWidth: setStrokeWidth, setObsColorEncoding, setFeatureValueColormap, setFeatureValueColormapRange, setTooltipsVisible, setTooltipCrosshairsVisible, setLegendVisible } = chanSetters;
265640
+ const pointValues = pointLayerCoordination[0]?.[pointScope] ?? {};
265641
+ const pointSetters = pointLayerCoordination[1]?.[pointScope] ?? {};
265642
+ const { spatialLayerVisible: pointVisible, spatialLayerOpacity: pointOpacity, spatialLayerColor: pointColor, obsColorEncoding: pointObsColorEncoding, featureValueColormap: pointFeatureValueColormap } = pointValues;
265643
+ const { setSpatialLayerVisible: setPointVisible, setSpatialLayerOpacity: setPointOpacity, setSpatialLayerColor: setPointColor, setObsColorEncoding: setPointObsColorEncoding, setFeatureValueColormap: setPointFeatureValueColormap, setFeatureValueColormapRange: setPointFeatureValueColormapRange } = pointSetters;
265644
+ const sharedOpacity = segOpacity ?? 1;
265645
+ const handleOpacityChange = useCallback((e3, v) => {
265646
+ setSegOpacity?.(v);
265647
+ setPointOpacity?.(v);
265648
+ }, [setSegOpacity, setPointOpacity]);
265649
+ const handleSegVisibleChange = useCallback(() => {
265650
+ setSegVisible?.(typeof segVisible === "boolean" ? !segVisible : false);
265651
+ }, [segVisible, setSegVisible]);
265652
+ const handlePointVisibleChange = useCallback(() => {
265653
+ setPointVisible?.(typeof pointVisible === "boolean" ? !pointVisible : false);
265654
+ }, [pointVisible, setPointVisible]);
265655
+ const handleColorEncodingChange = useCallback((e3) => {
265656
+ const val = e3.target.value;
265657
+ setObsColorEncoding(val);
265658
+ if (val === "geneSelection") {
265659
+ setPointObsColorEncoding?.("geneSelection");
265660
+ } else if (val === "cellSetSelection") {
265661
+ setPointObsColorEncoding?.("cellSetSelection");
265662
+ } else {
265663
+ setPointObsColorEncoding?.("spatialChannelColor");
265664
+ }
265665
+ }, [setObsColorEncoding, setPointObsColorEncoding]);
265666
+ const segVisibleSetting = typeof segVisible === "boolean" ? segVisible : true;
265667
+ const pointVisibleSetting = typeof pointVisible === "boolean" ? pointVisible : true;
265668
+ const SegVisibility = segVisibleSetting ? VisibilityIcon : VisibilityOffIcon;
265669
+ const PointVisibility = pointVisibleSetting ? VisibilityIcon : VisibilityOffIcon;
265670
+ const isStaticColor = obsColorEncoding === "spatialChannelColor";
265671
+ const isColormap = obsColorEncoding === "geneSelection";
265672
+ const pointIsStaticColor = pointObsColorEncoding === "spatialChannelColor";
265673
+ const pointIsColormap = pointObsColorEncoding === "geneSelection";
265674
+ return jsx$1(Grid$1, { className: lcClasses.layerControllerGrid, children: jsx$1(Paper, { elevation: 4, className: lcClasses.layerControllerRoot, children: jsxs(Grid$1, { container: true, direction: "row", justifyContent: "space-between", alignItems: "center", children: [jsxs(Grid$1, { size: "grow", style: { display: "flex", alignItems: "center", minWidth: 0 }, children: [jsx$1(Button, { onClick: handleSegVisibleChange, className: menuClasses.imageLayerVisibleButton, getAriaLabel: () => "Toggle mesh visibility", children: jsx$1(SegVisibility, {}) }), jsx$1(ChannelColorPickerMenu, { theme, color: segColor, setColor: setSegColor, isStaticColor, isColormap, featureValueColormap, visible: segVisibleSetting }), jsx$1(Typography, { className: `${menuClasses.imageLayerName} ${classes2.inlineLabel}`, children: "Cell" }), jsx$1("div", { className: classes2.divider }), jsx$1(Button, { onClick: handlePointVisibleChange, className: menuClasses.imageLayerVisibleButton, getAriaLabel: () => "Toggle centroid visibility", children: jsx$1(PointVisibility, {}) }), jsx$1(ChannelColorPickerMenu, { theme, color: pointColor, setColor: setPointColor, isStaticColor: pointIsStaticColor, isColormap: pointIsColormap, featureValueColormap: pointFeatureValueColormap, visible: pointVisibleSetting }), jsx$1(Typography, { className: `${menuClasses.imageLayerName} ${classes2.inlineLabel}`, children: "Point" })] }), jsx$1(Grid$1, { size: 2, children: jsx$1(Slider, { value: sharedOpacity, min: 0, max: 1, step: 1e-3, onChange: handleOpacityChange, className: menuClasses.imageLayerOpacitySlider, orientation: "horizontal", getAriaLabel: () => "Adjust shared opacity" }) }), jsx$1(Grid$1, { size: 1, children: jsx$1(CombinedEllipsisMenu, { obsType, featureType, featureValueType, filled, setFilled, strokeWidth, setStrokeWidth, obsColorEncoding, setObsColorEncoding, featureValueColormap, setFeatureValueColormap, featureValueColormapRange, setFeatureValueColormapRange, tooltipsVisible, setTooltipsVisible, tooltipCrosshairsVisible, setTooltipCrosshairsVisible, legendVisible, setLegendVisible, setPointObsColorEncoding, setPointFeatureValueColormap, setPointFeatureValueColormapRange, onColorEncodingChange: handleColorEncodingChange }) }), jsx$1(Grid$1, { size: 1, className: classes2.iconCell, children: jsx$1(SvgVector, { className: classes2.layerTypeCentroidSegmentationIcon }) })] }) }) });
265675
+ }
265481
265676
  function LayerController(props) {
265482
- const { theme, coordinationScopesRaw, segmentationLayerScopes, segmentationLayerCoordination, segmentationChannelScopesByLayer, segmentationChannelCoordination, images, imageLayerScopes, imageLayerCoordination, targetT, targetZ, setTargetT, setTargetZ, spatialRenderingMode, setSpatialRenderingMode, imageChannelScopesByLayer, imageChannelCoordination, spotLayerScopes, spotLayerCoordination, pointLayerScopes, pointLayerCoordination, pointMultiIndicesData, volumeLoadingStatus, tiledPointsLoadingProgress, layerPerFeatureForPoints } = props;
265677
+ const { theme, coordinationScopesRaw, segmentationLayerScopes, segmentationLayerCoordination, segmentationChannelScopesByLayer, segmentationChannelCoordination, images, imageLayerScopes, imageLayerCoordination, targetT, targetZ, setTargetT, setTargetZ, spatialRenderingMode, setSpatialRenderingMode, imageChannelScopesByLayer, imageChannelCoordination, spotLayerScopes, spotLayerCoordination, pointLayerScopes, pointLayerCoordination, pointMultiIndicesData, volumeLoadingStatus, tiledPointsLoadingProgress, layerPerFeatureForPoints, centroidSegmentationPairs = [], pairedSegScopes = /* @__PURE__ */ new Set(), pairedPointScopes = /* @__PURE__ */ new Set() } = props;
265483
265678
  const anyLayerHasT = Object.values(images || {}).some((image2) => image2?.image?.instance.hasTStack());
265484
265679
  const anyLayerHasZ = Object.values(images || {}).some((image2) => image2?.image?.instance.hasZStack());
265485
265680
  const maxT = Object.values(images || {}).reduce((a2, h2) => Math.max(a2, h2?.image?.instance.getNumT()), 1) - 1;
265486
265681
  const maxZ = Object.values(images || {}).reduce((a2, h2) => Math.max(a2, h2?.image?.instance.getNumZ()), 1) - 1;
265487
265682
  const reversedImageLayerScopes = useMemo$1(() => [...imageLayerScopes || []].reverse(), [imageLayerScopes]);
265488
- const reversedSegmentationLayerScopes = useMemo$1(() => [...segmentationLayerScopes || []].reverse(), [segmentationLayerScopes]);
265489
265683
  const reversedSpotLayerScopes = useMemo$1(() => [...spotLayerScopes || []].reverse(), [spotLayerScopes]);
265490
- const reversedPointLayerScopes = useMemo$1(() => [...pointLayerScopes || []].reverse(), [pointLayerScopes]);
265491
- return jsxs("div", { children: [anyLayerHasZ ? jsx$1(GlobalDimensionSlider, { label: "Z", targetValue: targetZ, setTargetValue: setTargetZ, max: maxZ, spatialRenderingMode, setSpatialRenderingMode }) : null, anyLayerHasT ? jsx$1(GlobalDimensionSlider, { label: "T", targetValue: targetT, setTargetValue: setTargetT, max: maxT }) : null, pointLayerScopes && reversedPointLayerScopes.map((layerScope) => jsx$1(PointLayerController, { theme, layerScope, layerCoordination: pointLayerCoordination[0][layerScope], setLayerCoordination: pointLayerCoordination[1][layerScope], pointMatrixIndicesData: pointMultiIndicesData?.[layerScope], tiledPointsLoadingProgress, layerPerFeatureForPoints }, layerScope)), spotLayerScopes && reversedSpotLayerScopes.map((layerScope) => jsx$1(SpotLayerController, { theme, layerScope, layerCoordination: spotLayerCoordination[0][layerScope], setLayerCoordination: spotLayerCoordination[1][layerScope] }, layerScope)), segmentationLayerScopes && reversedSegmentationLayerScopes.map((layerScope) => jsx$1(SegmentationLayerController, { theme, layerScope, layerCoordination: segmentationLayerCoordination[0][layerScope], setLayerCoordination: segmentationLayerCoordination[1][layerScope], channelScopes: segmentationChannelScopesByLayer[layerScope], channelCoordination: segmentationChannelCoordination[0][layerScope], setChannelCoordination: segmentationChannelCoordination[1][layerScope] }, layerScope)), imageLayerScopes && reversedImageLayerScopes.map((layerScope) => jsx$1(ImageLayerController, { theme, coordinationScopesRaw, layerScope, layerCoordination: imageLayerCoordination[0][layerScope], setLayerCoordination: imageLayerCoordination[1][layerScope], channelScopes: imageChannelScopesByLayer[layerScope], channelCoordination: imageChannelCoordination[0][layerScope], setChannelCoordination: imageChannelCoordination[1][layerScope], image: images[layerScope]?.image?.instance, featureIndex: images[layerScope]?.featureIndex, targetT, targetZ, spatialRenderingMode, volumeLoadingProgress: volumeLoadingStatus?.loadingProgress, onStopVolumeLoading: volumeLoadingStatus?.onStopLoading, onRestartVolumeLoading: volumeLoadingStatus?.onRestartLoading, volumeStillRef: volumeLoadingStatus?.stillRef }, layerScope))] });
265684
+ const reversedPointLayerScopes = useMemo$1(() => [...pointLayerScopes || []].filter((scope) => !pairedPointScopes.has(scope)).reverse(), [pointLayerScopes, pairedPointScopes]);
265685
+ const reversedSegmentationLayerScopes = useMemo$1(() => [...segmentationLayerScopes || []].filter((scope) => !pairedSegScopes.has(scope)).reverse(), [segmentationLayerScopes, pairedSegScopes]);
265686
+ return jsxs("div", { children: [anyLayerHasZ ? jsx$1(GlobalDimensionSlider, { label: "Z", targetValue: targetZ, setTargetValue: setTargetZ, max: maxZ, spatialRenderingMode, setSpatialRenderingMode }) : null, anyLayerHasT ? jsx$1(GlobalDimensionSlider, { label: "T", targetValue: targetT, setTargetValue: setTargetT, max: maxT }) : null, centroidSegmentationPairs.map(({ pointScope, segScope }) => jsx$1(SegmentationCentroidsController, { theme, segScope, segLayerCoordination: segmentationLayerCoordination, segChannelScopesByLayer: segmentationChannelScopesByLayer, segChannelCoordination: segmentationChannelCoordination, pointScope, pointLayerCoordination }, `combined-${segScope}-${pointScope}`)), pointLayerScopes && reversedPointLayerScopes.map((layerScope) => jsx$1(PointLayerController, { theme, layerScope, layerCoordination: pointLayerCoordination[0][layerScope], setLayerCoordination: pointLayerCoordination[1][layerScope], pointMatrixIndicesData: pointMultiIndicesData?.[layerScope], tiledPointsLoadingProgress, layerPerFeatureForPoints }, layerScope)), spotLayerScopes && reversedSpotLayerScopes.map((layerScope) => jsx$1(SpotLayerController, { theme, layerScope, layerCoordination: spotLayerCoordination[0][layerScope], setLayerCoordination: spotLayerCoordination[1][layerScope] }, layerScope)), segmentationLayerScopes && reversedSegmentationLayerScopes.map((layerScope) => jsx$1(SegmentationLayerController, { theme, layerScope, layerCoordination: segmentationLayerCoordination[0][layerScope], setLayerCoordination: segmentationLayerCoordination[1][layerScope], channelScopes: segmentationChannelScopesByLayer[layerScope], channelCoordination: segmentationChannelCoordination[0][layerScope], setChannelCoordination: segmentationChannelCoordination[1][layerScope] }, layerScope)), imageLayerScopes && reversedImageLayerScopes.map((layerScope) => jsx$1(ImageLayerController, { theme, coordinationScopesRaw, layerScope, layerCoordination: imageLayerCoordination[0][layerScope], setLayerCoordination: imageLayerCoordination[1][layerScope], channelScopes: imageChannelScopesByLayer[layerScope], channelCoordination: imageChannelCoordination[0][layerScope], setChannelCoordination: imageChannelCoordination[1][layerScope], image: images[layerScope]?.image?.instance, featureIndex: images[layerScope]?.featureIndex, targetT, targetZ, spatialRenderingMode, volumeLoadingProgress: volumeLoadingStatus?.loadingProgress, onStopVolumeLoading: volumeLoadingStatus?.onStopLoading, onRestartVolumeLoading: volumeLoadingStatus?.onRestartLoading, volumeStillRef: volumeLoadingStatus?.stillRef }, layerScope))] });
265492
265687
  }
265493
265688
  function LayerControllerSubscriber(props) {
265494
- const { coordinationScopes: coordinationScopesRaw, coordinationScopesBy: coordinationScopesByRaw, closeButtonVisible, downloadButtonVisible, removeGridComponent, theme, title: title2 = "Spatial Layers", uuid: uuid2, layerPerFeatureForPoints = false, cameraPresets } = props;
265689
+ const { coordinationScopes: coordinationScopesRaw, coordinationScopesBy: coordinationScopesByRaw, closeButtonVisible, downloadButtonVisible, removeGridComponent, theme, title: title2 = "Spatial Layers", uuid: uuid2, layerPerFeatureForPoints = false, cameraPresets, helpText = ViewHelpMapping.LAYER_CONTROLLER_BETA } = props;
265495
265690
  const loaders = useLoaders();
265496
265691
  const mergeCoordination = useMergeCoordination();
265497
265692
  const coordinationScopes = useCoordinationScopes(coordinationScopesRaw);
@@ -265579,7 +265774,8 @@ function LayerControllerSubscriber(props) {
265579
265774
  CoordinationType$1.TOOLTIPS_VISIBLE,
265580
265775
  CoordinationType$1.SPATIAL_CHANNEL_LABELS_VISIBLE,
265581
265776
  CoordinationType$1.SPATIAL_CHANNEL_LABELS_ORIENTATION,
265582
- CoordinationType$1.SPATIAL_CHANNEL_LABEL_SIZE
265777
+ CoordinationType$1.SPATIAL_CHANNEL_LABEL_SIZE,
265778
+ CoordinationType$1.SPATIAL_CHANNELS_SORT_ORDER
265583
265779
  ], coordinationScopes, coordinationScopesBy, CoordinationType$1.IMAGE_LAYER);
265584
265780
  const imageChannelCoordination = useComplexCoordinationSecondary([
265585
265781
  CoordinationType$1.SPATIAL_TARGET_C,
@@ -265620,6 +265816,40 @@ function LayerControllerSubscriber(props) {
265620
265816
  CoordinationType$1.TOOLTIP_CROSSHAIRS_VISIBLE,
265621
265817
  CoordinationType$1.LEGEND_VISIBLE
265622
265818
  ], coordinationScopes, coordinationScopesBy, CoordinationType$1.POINT_LAYER);
265819
+ const centroidSegmentationPairs = React__default.useMemo(() => {
265820
+ const pairs = [];
265821
+ const pairedSegScopes = /* @__PURE__ */ new Set();
265822
+ const pairedPointScopes = /* @__PURE__ */ new Set();
265823
+ pointLayerScopes.forEach((pointScope) => {
265824
+ const pointCoord = pointLayerCoordination[0][pointScope];
265825
+ if (!pointCoord)
265826
+ return;
265827
+ const pointObsType = pointCoord[CoordinationType$1.OBS_TYPE];
265828
+ if (!pointObsType)
265829
+ return;
265830
+ const matchingSegScope = segmentationLayerScopes.find((segScope) => {
265831
+ if (pairedSegScopes.has(segScope))
265832
+ return false;
265833
+ const channelScopes = segmentationChannelScopesByLayer[segScope] || [];
265834
+ return channelScopes.some((chanScope) => {
265835
+ const chanCoord = segmentationChannelCoordination[0]?.[segScope]?.[chanScope];
265836
+ return chanCoord?.[CoordinationType$1.OBS_TYPE] === pointObsType;
265837
+ });
265838
+ });
265839
+ if (matchingSegScope) {
265840
+ pairs.push({ pointScope, segScope: matchingSegScope });
265841
+ pairedSegScopes.add(matchingSegScope);
265842
+ pairedPointScopes.add(pointScope);
265843
+ }
265844
+ });
265845
+ return { pairs, pairedSegScopes, pairedPointScopes };
265846
+ }, [
265847
+ pointLayerScopes,
265848
+ pointLayerCoordination,
265849
+ segmentationLayerScopes,
265850
+ segmentationChannelScopesByLayer,
265851
+ segmentationChannelCoordination
265852
+ ]);
265623
265853
  const [{ volumeLoadingProgress: volumeLoadingStatus, tiledPointsLoadingProgress }] = useAuxiliaryCoordination([
265624
265854
  "spatialAcceleratedVolumeLoadingProgress",
265625
265855
  "spatialTiledPointsLoadingProgress"
@@ -265647,7 +265877,7 @@ function LayerControllerSubscriber(props) {
265647
265877
  imageDataStatus,
265648
265878
  pointMultiIndicesDataStatus
265649
265879
  ]);
265650
- return jsx$1(TitleInfo, { title: title2, isScroll: true, closeButtonVisible, downloadButtonVisible, removeGridComponent, theme, isReady, errors, children: jsx$1(LayerController, { theme, coordinationScopesRaw, segmentationLayerScopes, segmentationLayerCoordination, segmentationChannelScopesByLayer, segmentationChannelCoordination, images: imageData, imageLayerScopes, imageLayerCoordination, targetT, targetZ, setTargetT, setTargetZ, spatialRenderingMode, setSpatialRenderingMode, imageChannelScopesByLayer, imageChannelCoordination, spotLayerScopes, spotLayerCoordination, pointLayerScopes, pointLayerCoordination, pointMultiIndicesData, layerPerFeatureForPoints, volumeLoadingStatus, tiledPointsLoadingProgress }) });
265880
+ return jsx$1(TitleInfo, { title: title2, isScroll: true, closeButtonVisible, downloadButtonVisible, removeGridComponent, theme, isReady, errors, helpText, children: jsx$1(LayerController, { theme, coordinationScopesRaw, segmentationLayerScopes, segmentationLayerCoordination, segmentationChannelScopesByLayer, segmentationChannelCoordination, images: imageData, imageLayerScopes, imageLayerCoordination, targetT, targetZ, setTargetT, setTargetZ, spatialRenderingMode, setSpatialRenderingMode, imageChannelScopesByLayer, imageChannelCoordination, spotLayerScopes, spotLayerCoordination, pointLayerScopes, pointLayerCoordination, pointMultiIndicesData, layerPerFeatureForPoints, volumeLoadingStatus, tiledPointsLoadingProgress, centroidSegmentationPairs: centroidSegmentationPairs.pairs, pairedSegScopes: centroidSegmentationPairs.pairedSegScopes, pairedPointScopes: centroidSegmentationPairs.pairedPointScopes }) });
265651
265881
  }
265652
265882
  const isIterable$2 = (obj) => Symbol.iterator in obj;
265653
265883
  const hasIterableEntries = (value2) => (
@@ -267562,7 +267792,7 @@ function HiglassGlobalStyles(props) {
267562
267792
  }
267563
267793
  register({ dataFetcher: ZarrMultivecDataFetcher_default, config: ZarrMultivecDataFetcher_default.config }, { pluginType: "dataFetcher" });
267564
267794
  const LazyHiGlassComponent = React__default.lazy(async () => {
267565
- const { HiGlassComponent } = await import("./higlass-DL7cd1YR.js");
267795
+ const { HiGlassComponent } = await import("./higlass-4OKx7mdy.js");
267566
267796
  return { default: HiGlassComponent };
267567
267797
  });
267568
267798
  const HG_SIZE = 800;
@@ -267900,6 +268130,12 @@ const useStyles$4 = makeStyles()(() => ({
267900
268130
  neuroglancerWrapper: {
267901
268131
  position: "relative",
267902
268132
  height: "100%"
268133
+ },
268134
+ meshLoadingIndicator: {
268135
+ position: "absolute",
268136
+ bottom: 8,
268137
+ left: 8,
268138
+ zIndex: 50
267903
268139
  }
267904
268140
  }));
267905
268141
  const globalNeuroglancerCss = `
@@ -269151,13 +269387,16 @@ const globalNeuroglancerStyles = {
269151
269387
  },
269152
269388
  "input.perspective-panel-show-slice-views": { pointerEvents: "all" },
269153
269389
  ".neuroglancer-rendered-data-panel": {
269154
- cursor: "crosshair",
269390
+ cursor: "grab",
269155
269391
  position: "relative",
269156
269392
  outline: "0",
269157
269393
  touchAction: "none",
269158
269394
  color: "#fff",
269159
269395
  textAlign: "left"
269160
269396
  },
269397
+ ".neuroglancer-rendered-data-panel:active": {
269398
+ cursor: "grabbing"
269399
+ },
269161
269400
  ".neuroglancer-display-dimensions-widget": {
269162
269401
  position: "absolute",
269163
269402
  cursor: "default",
@@ -270510,7 +270749,7 @@ function NeuroglancerGlobalStyles(props) {
270510
270749
  const { classes: classes2 } = props;
270511
270750
  return jsxs(Fragment, { children: [jsx$1(GlobalStyles$3, { styles: globalNeuroglancerCss }), jsx$1(ScopedGlobalStyles, { styles: globalNeuroglancerStyles, parentClassName: classes2.neuroglancerWrapper })] });
270512
270751
  }
270513
- const LazyReactNeuroglancer = React__default.lazy(() => import("./ReactNeuroglancer-Do72MK3I.js"));
270752
+ const LazyReactNeuroglancer = React__default.lazy(() => import("./ReactNeuroglancer-DSiLDSuM.js"));
270514
270753
  function createWorker() {
270515
270754
  const worker = new WorkerFactory$1();
270516
270755
  worker.AsyncComputationWorker = WorkerFactory;
@@ -270555,12 +270794,31 @@ class NeuroglancerComp extends PureComponent {
270555
270794
  }
270556
270795
  };
270557
270796
  remapWheelToZoom(viewer.inputEventBindings.perspectiveView);
270797
+ const { getMeshIdToCellId, onViewerReady, cellsUrl } = this.props;
270558
270798
  this.prevHoverHandler = () => {
270559
- if (viewer.mouseState.pickedValue !== void 0) {
270560
- const pickedSegment = viewer.mouseState.pickedValue;
270561
- this.latestOnSelectHoveredCoords?.(pickedSegment?.low);
270799
+ const ms = viewer.mouseState;
270800
+ if (ms.pickedAnnotationId != null) {
270801
+ const byIdKey = String(ms.pickedAnnotationId);
270802
+ fetch(`${cellsUrl}/by_id/${byIdKey}`).then((r3) => r3.arrayBuffer()).then((buf) => {
270803
+ const dv = new DataView(buf);
270804
+ const actualMeshId = String(dv.getInt32(12, true));
270805
+ const cellId = getMeshIdToCellId?.(actualMeshId) ?? actualMeshId;
270806
+ this.latestOnSelectHoveredCoords?.(cellId);
270807
+ });
270808
+ return;
270809
+ }
270810
+ if (ms.pickedValue !== void 0 && ms.pickedValue !== null) {
270811
+ const meshId = String(ms.pickedValue?.low);
270812
+ const cellId = getMeshIdToCellId?.(meshId) ?? meshId;
270813
+ this.latestOnSelectHoveredCoords?.(cellId);
270814
+ return;
270562
270815
  }
270816
+ this.latestOnSelectHoveredCoords?.(null);
270563
270817
  };
270818
+ onViewerReady?.(() => {
270819
+ const panel = [...viewer.display.panels][0];
270820
+ return panel?.projectionParameters?.value_?.viewProjectionMat;
270821
+ });
270564
270822
  viewer.mouseState.changed.add(this.prevHoverHandler);
270565
270823
  } else {
270566
270824
  if (this.prevMouseStateChanged && this.prevHoverHandler) {
@@ -270581,8 +270839,8 @@ class NeuroglancerComp extends PureComponent {
270581
270839
  }
270582
270840
  }
270583
270841
  render() {
270584
- const { classes: classes2, viewerState, cellColorMapping, onLayerLoadingChange } = this.props;
270585
- return jsxs(Fragment, { children: [jsx$1(NeuroglancerGlobalStyles, { classes: classes2 }), jsx$1("div", { className: classes2.neuroglancerWrapper, children: jsx$1(Suspense, { fallback: jsx$1("div", { children: "Loading..." }), children: jsx$1(LazyReactNeuroglancer, { brainMapsClientId: "NOT_A_VALID_ID", viewerState, onViewerStateChanged: this.onViewerStateChanged, onLayerLoadingChange, bundleRoot: this.bundleRoot, cellColorMapping, ref: this.onRef }) }) })] });
270842
+ const { classes: classes2, viewerState, cellColorMapping, onLayerLoadingChange, onAnnotationSourceReady, onViewerReady, meshOpacity } = this.props;
270843
+ return jsxs(Fragment, { children: [jsx$1(NeuroglancerGlobalStyles, { classes: classes2 }), jsx$1("div", { className: classes2.neuroglancerWrapper, children: jsx$1(Suspense, { fallback: jsx$1("div", { children: "Loading..." }), children: jsx$1(LazyReactNeuroglancer, { brainMapsClientId: "NOT_A_VALID_ID", viewerState, onViewerStateChanged: this.onViewerStateChanged, onLayerLoadingChange, bundleRoot: this.bundleRoot, cellColorMapping, ref: this.onRef, onAnnotationSourceReady, onViewerReady, meshOpacity }) }) })] });
270586
270844
  }
270587
270845
  }
270588
270846
  function useMemoCustomComparison(factory2, dependencies2, customIsEqual) {
@@ -270597,17 +270855,22 @@ function useMemoCustomComparison(factory2, dependencies2, customIsEqual) {
270597
270855
  }
270598
270856
  const shallowDiff = (prevDeps, nextDeps, depName) => prevDeps[depName] !== nextDeps[depName];
270599
270857
  const shallowDiffByLayer = (prevDeps, nextDeps, depName, scopeName) => prevDeps?.[depName]?.[scopeName] !== nextDeps?.[depName]?.[scopeName];
270858
+ const shallowDiffByChannel = (prevDeps, nextDeps, depName, firstName, secondName) => prevDeps?.[depName]?.[firstName]?.[secondName] !== nextDeps?.[depName]?.[firstName]?.[secondName];
270600
270859
  const shallowDiffByChannelWithKeys = (prevDeps, nextDeps, depName, firstName, secondName, keys2) => keys2.some((k) => prevDeps?.[depName]?.[firstName]?.[secondName]?.[k] !== nextDeps?.[depName]?.[firstName]?.[secondName]?.[k]);
270601
270860
  const shallowDiffByLayerCoordinationWithKeys = (prevDeps, nextDeps, depName, layerScope, keys2) => keys2.some((k) => prevDeps?.[depName]?.[0]?.[layerScope]?.[k] !== nextDeps?.[depName]?.[0]?.[layerScope]?.[k]);
270602
270861
  const shallowDiffByChannelCoordinationWithKeys = (prevDeps, nextDeps, depName, layerScope, channelScope, keys2) => keys2.some((k) => prevDeps?.[depName]?.[0]?.[layerScope]?.[channelScope]?.[k] !== nextDeps?.[depName]?.[0]?.[layerScope]?.[channelScope]?.[k]);
270603
270862
  function customIsEqualForCellColors(prevDeps, nextDeps) {
270604
270863
  let forceUpdate = false;
270605
270864
  const curriedShallowDiff = (depName) => shallowDiff(prevDeps, nextDeps, depName);
270865
+ const curriedShallowDiffByChannel = (depName, firstName, secondName) => shallowDiffByChannel(prevDeps, nextDeps, depName, firstName, secondName);
270606
270866
  const curriedShallowDiffByChannelWithKeys = (depName, firstName, secondName, keys2) => shallowDiffByChannelWithKeys(prevDeps, nextDeps, depName, firstName, secondName, keys2);
270607
270867
  const curriedShallowDiffByChannelCoordinationWithKeys = (depName, layerScope, channelScope, keys2) => shallowDiffByChannelCoordinationWithKeys(prevDeps, nextDeps, depName, layerScope, channelScope, keys2);
270608
270868
  if (curriedShallowDiff("theme")) {
270609
270869
  forceUpdate = true;
270610
270870
  }
270871
+ if (curriedShallowDiff("csvLoaded")) {
270872
+ forceUpdate = true;
270873
+ }
270611
270874
  if (["segmentationLayerScopes", "segmentationChannelScopesByLayer"].some(curriedShallowDiff)) {
270612
270875
  forceUpdate = true;
270613
270876
  } else {
@@ -270622,8 +270885,11 @@ function customIsEqualForCellColors(prevDeps, nextDeps) {
270622
270885
  "obsSetSelection",
270623
270886
  "additionalObsSets",
270624
270887
  "spatialChannelColor",
270625
- "spatialChannelOpacity"
270626
- ])) {
270888
+ "spatialChannelOpacity",
270889
+ "featureValueColormap",
270890
+ "featureValueColormapRange",
270891
+ "featureSelection"
270892
+ ]) || curriedShallowDiffByChannel("segmentationMultiIndicesData", layerScope, channelScope) || curriedShallowDiffByChannel("segmentationMultiExpressionNormData", layerScope, channelScope)) {
270627
270893
  forceUpdate = true;
270628
270894
  }
270629
270895
  });
@@ -270669,7 +270935,13 @@ function customIsEqualForInitialViewerState(prevDeps, nextDeps) {
270669
270935
  "featureSelection",
270670
270936
  "featureFilterMode",
270671
270937
  "featureColor",
270672
- "spatialPointStrokeWidth"
270938
+ "spatialPointStrokeWidth",
270939
+ "featureValueColormap",
270940
+ "featureValueColormapRange",
270941
+ "featureSelection",
270942
+ "obsSetColor",
270943
+ "obsSetSelection",
270944
+ "additionalObsSets"
270673
270945
  ]) || Math.abs(prevDeps?.pointLayerCoordination?.[0]?.[layerScope]?.spatialLayerOpacity - nextDeps?.pointLayerCoordination?.[0]?.[layerScope]?.spatialLayerOpacity) >= 0.05) {
270674
270946
  forceUpdate = true;
270675
270947
  }
@@ -270723,6 +270995,35 @@ function getSpatialLayerColorWithSelectionShader(staticColor, opacity2, featureI
270723
270995
  }
270724
270996
  `;
270725
270997
  }
270998
+ function getObsSetSelectionFilteredShader(phenotypeColors, selectedIndices, opacity2, borderWidth = 0) {
270999
+ const numColors = phenotypeColors.length;
271000
+ const numSelected = selectedIndices.length;
271001
+ const normColors = phenotypeColors.map((c2) => normalizeColor(c2));
271002
+ const colorsDecl = `vec3 phenotypeColors[${numColors}] = vec3[${numColors}](${normColors.map((c2) => toVec3(c2)).join(", ")});`;
271003
+ const selectedDecl = `int selectedIndices[${numSelected}] = int[${numSelected}](${selectedIndices.join(", ")});`;
271004
+ return `
271005
+ void main() {
271006
+ int phenotype = prop_phenotype();
271007
+ ${colorsDecl}
271008
+ ${selectedDecl}
271009
+ bool isSelected = false;
271010
+ for (int i = 0; i < ${numSelected}; ++i) {
271011
+ if (phenotype == selectedIndices[i]) {
271012
+ isSelected = true;
271013
+ }
271014
+ }
271015
+ if (!isSelected) {
271016
+ discard;
271017
+ }
271018
+ vec3 color = vec3(0.5, 0.5, 0.5);
271019
+ if (phenotype >= 0 && phenotype < ${numColors}) {
271020
+ color = phenotypeColors[phenotype];
271021
+ }
271022
+ setColor(vec4(color, ${opacity2.toFixed(4)}));
271023
+ ${borderWidthGlsl(borderWidth)}
271024
+ }
271025
+ `;
271026
+ }
270726
271027
  function getSpatialLayerColorFilteredShader(staticColor, opacity2, featureIndices, featureIndexProp, borderWidth = 0) {
270727
271028
  const normStatic = normalizeColor(staticColor);
270728
271029
  const numFeatures = featureIndices.length;
@@ -270904,6 +271205,23 @@ function getRandomPerPointShader(opacity2, featureIndexProp, pointIndexProp, bor
270904
271205
  }
270905
271206
  `;
270906
271207
  }
271208
+ function getQuantitativeColormapShader(quantitativeColorProp, opacity2, colormap = GLSL_COLORMAP_DEFAULT, colormapRange = [0, 1], borderWidth = 0, quantitativeColorMax = 1) {
271209
+ const [low, high] = colormapRange;
271210
+ const resolvedColormap = GLSL_COLORMAPS.includes(colormap) ? colormap : GLSL_COLORMAP_DEFAULT;
271211
+ return `
271212
+ #define COLORMAP_FUNC ${resolvedColormap}
271213
+ ${colormaps$1}
271214
+ void main() {
271215
+ float rawVal = float(prop_${quantitativeColorProp}());
271216
+ float normalizedVal = rawVal / ${quantitativeColorMax.toFixed(4)};
271217
+ float t = (normalizedVal - ${low.toFixed(4)}) / max(${(high - low).toFixed(4)}, 0.0001);
271218
+ t = clamp(t, 0.0, 1.0);
271219
+ vec4 col = COLORMAP_FUNC(t);
271220
+ setColor(vec4(col.rgb, ${opacity2.toFixed(4)}));
271221
+ ${borderWidthGlsl(borderWidth)}
271222
+ }
271223
+ `;
271224
+ }
270907
271225
  function getRandomPerPointWithSelectionShader(featureIndices, defaultColor, opacity2, featureIndexProp, pointIndexProp, borderWidth = 0) {
270908
271226
  const normDefault = normalizeColor(defaultColor);
270909
271227
  const numFeatures = featureIndices.length;
@@ -270959,8 +271277,25 @@ function getRandomPerPointFilteredShader(featureIndices, opacity2, featureIndexP
270959
271277
  }
270960
271278
  `;
270961
271279
  }
271280
+ function getObsSetSelectionShader(phenotypeColors, opacity2, borderWidth = 0) {
271281
+ const numColors = phenotypeColors.length;
271282
+ const normColors = phenotypeColors.map((c2) => normalizeColor(c2));
271283
+ const colorsDecl = `vec3 phenotypeColors[${numColors}] = vec3[${numColors}](${normColors.map((c2) => toVec3(c2)).join(", ")});`;
271284
+ return `
271285
+ void main() {
271286
+ int phenotype = prop_phenotype();
271287
+ ${colorsDecl}
271288
+ vec3 color = ${toVec3(normalizeColor([128, 128, 128]))};
271289
+ if (phenotype >= 0 && phenotype < ${numColors}) {
271290
+ color = phenotypeColors[phenotype];
271291
+ }
271292
+ setColor(vec4(color, ${opacity2.toFixed(4)}));
271293
+ ${borderWidthGlsl(borderWidth)}
271294
+ }
271295
+ `;
271296
+ }
270962
271297
  function getPointsShader(layerCoordination) {
270963
- const { theme, featureIndex, spatialLayerOpacity, obsColorEncoding, spatialLayerColor, featureSelection, featureFilterMode, featureColor, pointMarkerBorderWidth = 0, featureIndexProp, pointIndexProp } = layerCoordination;
271298
+ const { theme, featureIndex, spatialLayerOpacity, obsColorEncoding, spatialLayerColor, featureSelection, featureFilterMode, featureColor, pointMarkerBorderWidth = 0, featureIndexProp, pointIndexProp, quantitativeColorProp, featureValueColormap, featureValueColormapRange, obsSetColor, obsSetSelection, quantitativeColorMax, pointsAreSegmentationCentroids } = layerCoordination;
270964
271299
  const defaultColor = getDefaultColor(theme);
270965
271300
  const opacity2 = spatialLayerOpacity ?? 1;
270966
271301
  const staticColor = Array.isArray(spatialLayerColor) && spatialLayerColor.length === 3 ? spatialLayerColor : defaultColor;
@@ -270984,7 +271319,7 @@ function getPointsShader(layerCoordination) {
270984
271319
  }
270985
271320
  return getSpatialLayerColorWithSelectionShader(staticColor, opacity2, featureIndices, defaultColor, featureIndexProp, pointMarkerBorderWidth);
270986
271321
  }
270987
- if (obsColorEncoding === "geneSelection") {
271322
+ if (obsColorEncoding === "geneSelection" && !pointsAreSegmentationCentroids) {
270988
271323
  if (!featureIndexProp) {
270989
271324
  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.");
270990
271325
  }
@@ -271020,19 +271355,49 @@ function getPointsShader(layerCoordination) {
271020
271355
  }
271021
271356
  return getRandomPerPointWithSelectionShader(featureIndices, defaultColor, opacity2, featureIndexProp, pointIndexProp, pointMarkerBorderWidth);
271022
271357
  }
271358
+ if (obsColorEncoding === "geneSelection" && pointsAreSegmentationCentroids) {
271359
+ if (!quantitativeColorProp) {
271360
+ console.warn("In order to use quantitative colormap encoding for Neuroglancer Points, options.quantitativeColorProp must be specified for the obsPoints.ng-annotations fileType in the Vitessce configuration. Falling back to static color.");
271361
+ return getSpatialLayerColorShader(staticColor, opacity2, pointMarkerBorderWidth);
271362
+ }
271363
+ return getQuantitativeColormapShader(quantitativeColorProp, opacity2, featureValueColormap, featureValueColormapRange, pointMarkerBorderWidth, quantitativeColorMax);
271364
+ }
271365
+ if (obsColorEncoding === "cellSetSelection") {
271366
+ if (!obsSetColor || !obsSetSelection) {
271367
+ return getSpatialLayerColorShader(staticColor, opacity2, pointMarkerBorderWidth);
271368
+ }
271369
+ const colorByName = {};
271370
+ obsSetColor?.forEach(({ path: path2, color: color2 }) => {
271371
+ if (path2?.length > 1) {
271372
+ colorByName[path2[path2.length - 1]] = color2;
271373
+ }
271374
+ });
271375
+ const uniqueNames = Object.keys(colorByName).sort();
271376
+ const phenotypeColors = uniqueNames.map((name2) => colorByName[name2] ?? defaultColor);
271377
+ if (isFiltered) {
271378
+ const selectedNames = new Set(obsSetSelection?.map((path2) => path2[path2.length - 1]) ?? []);
271379
+ const selectedIndices = uniqueNames.map((name2, idx) => selectedNames.has(name2) ? idx : -1).filter((idx) => idx >= 0);
271380
+ if (selectedIndices.length === 0) {
271381
+ return "void main() { discard; }";
271382
+ }
271383
+ return getObsSetSelectionFilteredShader(phenotypeColors, selectedIndices, opacity2, pointMarkerBorderWidth);
271384
+ }
271385
+ return getObsSetSelectionShader(phenotypeColors, opacity2, pointMarkerBorderWidth);
271386
+ }
271023
271387
  return getSpatialLayerColorShader(staticColor, opacity2, pointMarkerBorderWidth);
271024
271388
  }
271389
+ const DEFAULT_NG_DIMENSIONS = {
271390
+ x: [1, "nm"],
271391
+ y: [1, "nm"],
271392
+ z: [1, "nm"]
271393
+ };
271025
271394
  const DEFAULT_NG_PROPS = {
271026
271395
  layout: "3d",
271027
271396
  position: [0, 0, 0],
271028
271397
  projectionOrientation: [0, 0, 0, 1],
271029
271398
  projectionScale: 1024,
271030
271399
  crossSectionScale: 1,
271031
- dimensions: {
271032
- x: [1, "nm"],
271033
- y: [1, "nm"],
271034
- z: [1, "nm"]
271035
- },
271400
+ dimensions: DEFAULT_NG_DIMENSIONS,
271036
271401
  layers: []
271037
271402
  };
271038
271403
  function toPrecomputedSource(url) {
@@ -271050,6 +271415,15 @@ function toNgLayerName(dataType, layerScope, channelScope = null) {
271050
271415
  }
271051
271416
  throw new Error(`Unsupported data type: ${dataType}`);
271052
271417
  }
271418
+ function pointsHaveMatchingSegmentation({ pointObsType, segmentationLayerScopes, segmentationChannelScopesByLayer, segmentationChannelCoordination }) {
271419
+ return segmentationLayerScopes.some((segmentationLayerScope) => {
271420
+ const segmentationChannelScopes = segmentationChannelScopesByLayer?.[segmentationLayerScope] || [];
271421
+ return segmentationChannelScopes.some((segmentationChannelScope) => {
271422
+ const segmentationObsType = segmentationChannelCoordination?.[0]?.[segmentationLayerScope]?.[segmentationChannelScope]?.obsType;
271423
+ return segmentationObsType && pointObsType && segmentationObsType === pointObsType;
271424
+ });
271425
+ });
271426
+ }
271053
271427
  function useNeuroglancerViewerState(theme, showAxisLines, segmentationLayerScopes, segmentationChannelScopesByLayer, segmentationLayerCoordination, segmentationChannelCoordination, obsSegmentationsUrls, obsSegmentationsData, pointLayerScopes, pointLayerCoordination, obsPointsUrls, obsPointsData, pointMultiIndicesData) {
271054
271428
  const viewerState = useMemoCustomComparison(() => {
271055
271429
  let result = cloneDeep$1(DEFAULT_NG_PROPS);
@@ -271072,6 +271446,9 @@ function useNeuroglancerViewerState(theme, showAxisLines, segmentationLayerScope
271072
271446
  } : toPrecomputedSource(layerUrl);
271073
271447
  result = {
271074
271448
  ...result,
271449
+ showAxisLines,
271450
+ showDefaultAnnotations: false,
271451
+ // this removes the yellow box around pointsLayer
271075
271452
  layers: [
271076
271453
  ...result.layers,
271077
271454
  {
@@ -271095,9 +271472,27 @@ function useNeuroglancerViewerState(theme, showAxisLines, segmentationLayerScope
271095
271472
  const layerCoordination = pointLayerCoordination[0][layerScope];
271096
271473
  const layerData = obsPointsData[layerScope];
271097
271474
  const layerUrl = obsPointsUrls[layerScope]?.[0]?.url;
271475
+ const ngOptions = layerData?.neuroglancerOptions;
271476
+ if (ngOptions?.transform?.matrix && ngOptions?.transform?.outputDimensions) {
271477
+ result = {
271478
+ ...result,
271479
+ dimensions: ngOptions.transform.outputDimensions ?? DEFAULT_NG_DIMENSIONS
271480
+ };
271481
+ }
271482
+ const pointsAreSegmentationCentroids = pointsHaveMatchingSegmentation({
271483
+ pointObsType: layerCoordination?.obsType,
271484
+ segmentationLayerScopes,
271485
+ segmentationChannelScopesByLayer,
271486
+ segmentationChannelCoordination
271487
+ });
271098
271488
  const featureIndex = pointMultiIndicesData[layerScope]?.featureIndex;
271099
271489
  if (layerUrl && layerData) {
271100
- const { spatialLayerVisible, spatialLayerOpacity, obsColorEncoding, spatialLayerColor, featureSelection, featureFilterMode, featureColor, spatialPointStrokeWidth } = layerCoordination || {};
271490
+ const { spatialLayerVisible, spatialLayerOpacity, obsColorEncoding, spatialLayerColor, featureSelection, featureFilterMode, featureColor, spatialPointStrokeWidth, featureValueColormap, featureValueColormapRange } = layerCoordination || {};
271491
+ const quantitativeColorMax = layerData.neuroglancerOptions?.quantitativeColorMax;
271492
+ if (!quantitativeColorMax && obsColorEncoding === "geneSelection") {
271493
+ console.warn("quantitativeColorMax not specified in options — defaulting to 1.0 (no normalization)");
271494
+ }
271495
+ const quantitativeColorMaxSelected = quantitativeColorMax ?? 1;
271101
271496
  const shader = getPointsShader({
271102
271497
  theme,
271103
271498
  featureIndex,
@@ -271107,9 +271502,18 @@ function useNeuroglancerViewerState(theme, showAxisLines, segmentationLayerScope
271107
271502
  featureSelection,
271108
271503
  featureFilterMode,
271109
271504
  featureColor,
271505
+ featureValueColormap,
271506
+ featureValueColormapRange,
271110
271507
  featureIndexProp: layerData.neuroglancerOptions?.featureIndexProp,
271111
271508
  pointIndexProp: layerData.neuroglancerOptions?.pointIndexProp,
271112
- pointMarkerBorderWidth: spatialPointStrokeWidth ?? 0
271509
+ quantitativeColorProp: layerData.neuroglancerOptions?.quantitativeColorProp,
271510
+ pointMarkerBorderWidth: spatialPointStrokeWidth ?? 0,
271511
+ obsSets: layerCoordination.obsSets,
271512
+ obsSetColor: layerCoordination.obsSetColor,
271513
+ obsSetSelection: layerCoordination.obsSetSelection,
271514
+ additionalObsSets: layerCoordination.additionalObsSets,
271515
+ quantitativeColorMax: quantitativeColorMaxSelected,
271516
+ pointsAreSegmentationCentroids
271113
271517
  });
271114
271518
  result = {
271115
271519
  ...result,
@@ -271122,7 +271526,13 @@ function useNeuroglancerViewerState(theme, showAxisLines, segmentationLayerScope
271122
271526
  subsources: {
271123
271527
  default: true
271124
271528
  },
271125
- enableDefaultSubsources: false
271529
+ enableDefaultSubsources: false,
271530
+ ...ngOptions?.transform?.matrix ? {
271531
+ transform: {
271532
+ matrix: ngOptions.transform.matrix,
271533
+ outputDimensions: ngOptions.transform?.outputDimensions ?? result.dimensions
271534
+ }
271535
+ } : {}
271126
271536
  },
271127
271537
  tab: "annotations",
271128
271538
  shader,
@@ -271130,7 +271540,7 @@ function useNeuroglancerViewerState(theme, showAxisLines, segmentationLayerScope
271130
271540
  visible: spatialLayerVisible,
271131
271541
  // Options from layerData.neuroglancerOptions
271132
271542
  // like projectionAnnotationSpacing:
271133
- projectionAnnotationSpacing: layerData.neuroglancerOptions?.projectionAnnotationSpacing ?? 1
271543
+ projectionAnnotationSpacing: ngOptions?.projectionAnnotationSpacing ?? 1
271134
271544
  }
271135
271545
  ],
271136
271546
  // TODO: is this needed?
@@ -271161,6 +271571,8 @@ function useNeuroglancerViewerState(theme, showAxisLines, segmentationLayerScope
271161
271571
  }, customIsEqualForInitialViewerState);
271162
271572
  return viewerState;
271163
271573
  }
271574
+ const ANNOTATION_HEADER_OFFSET = 8;
271575
+ const GREY_HEX = "#323232";
271164
271576
  const EPSILON_KEYS_MAPPING_NG = {
271165
271577
  projectionScale: 100,
271166
271578
  projectionOrientation: 0.02,
@@ -271181,6 +271593,17 @@ const multiplyQuat = (a2, b2) => {
271181
271593
  const conjQuat = (q) => [-q[0], -q[1], -q[2], q[3]];
271182
271594
  const rad2deg = (r3) => r3 * 180 / Math.PI;
271183
271595
  const deg2rad = (d) => d * Math.PI / 180;
271596
+ function applyColormap(colormap, t4) {
271597
+ const tClamped = Math.max(0, Math.min(1, t4));
271598
+ const interpolators = {
271599
+ plasma: interpolatePlasma,
271600
+ viridis: interpolateViridis,
271601
+ jet: interpolateJet,
271602
+ greys: interpolateGreys
271603
+ };
271604
+ const fn2 = interpolators[colormap] ?? interpolateViridis;
271605
+ return fn2(tClamped);
271606
+ }
271184
271607
  function valueGreaterThanEpsilon(a2, b2, epsilon3) {
271185
271608
  if (Array.isArray(a2) && Array.isArray(b2) && a2.length === b2.length) {
271186
271609
  return a2.some((val, i2) => Math.abs(val - b2[i2]) > epsilon3);
@@ -271247,13 +271670,57 @@ function makeVitNgZoomCalibrator(initialNgProjectionScale, initialDeckZoom = 0)
271247
271670
  }
271248
271671
  };
271249
271672
  }
271673
+ function parseAnnotationChunkSegmentsWithPositions(buffer2, serializer2) {
271674
+ const dv = new DataView(buffer2);
271675
+ if (buffer2.byteLength <= 8)
271676
+ return [];
271677
+ const count2 = dv.getUint32(0, true);
271678
+ if (count2 === 0)
271679
+ return [];
271680
+ const numBytes = serializer2.serializedBytes;
271681
+ const properties2 = [null, null, null, null, null];
271682
+ const results = [];
271683
+ for (let i2 = 0; i2 < count2; i2++) {
271684
+ serializer2.deserialize(dv, ANNOTATION_HEADER_OFFSET, i2, count2, true, properties2);
271685
+ const phenotype = properties2[0];
271686
+ const segId = properties2[1];
271687
+ if (segId > 0) {
271688
+ const propBase = ANNOTATION_HEADER_OFFSET + i2 * numBytes;
271689
+ const x2 = dv.getFloat32(propBase + 0, true);
271690
+ const y2 = dv.getFloat32(propBase + 4, true);
271691
+ const z2 = dv.getFloat32(propBase + 8, true);
271692
+ if (x2 !== 0 || y2 !== 0 || z2 !== 0) {
271693
+ results.push({
271694
+ id: String(segId),
271695
+ // mesh segment ID - use for segments array
271696
+ phenotype: Number(phenotype),
271697
+ x: x2,
271698
+ y: y2,
271699
+ z: z2
271700
+ });
271701
+ }
271702
+ }
271703
+ }
271704
+ return results;
271705
+ }
271706
+ const remapCellColors = (ngCellColors, cellIdToMeshIdRef) => {
271707
+ const remapped = {};
271708
+ Object.entries(ngCellColors).forEach(([cellId, color2]) => {
271709
+ const meshId = cellIdToMeshIdRef.current[cellId] ?? cellId;
271710
+ remapped[meshId] = color2;
271711
+ });
271712
+ return remapped;
271713
+ };
271250
271714
  const VITESSCE_INTERACTION_DELAY = 50;
271251
271715
  const INIT_VIT_ZOOM = -3.6;
271252
271716
  const ZOOM_EPS = 0.01;
271253
271717
  const ROTATION_EPS = 1e-3;
271254
271718
  const TARGET_EPS = 0.5;
271255
271719
  const NG_ROT_COOLDOWN_MS = 120;
271720
+ const MESH_LOAD_THRESHOLD = 100;
271721
+ const MESH_LOADING_OVERLAY_TIMEOUT = 1500;
271256
271722
  const GUIDE_URL = "https://vitessce.io/docs/ng-guide/";
271723
+ const MESH_OPACITY = 0.6;
271257
271724
  const LAST_INTERACTION_SOURCE = {
271258
271725
  vitessce: "vitessce",
271259
271726
  neuroglancer: "neuroglancer"
@@ -271270,10 +271737,11 @@ function NeuroglancerSubscriber(props) {
271270
271737
  downloadButtonVisible,
271271
271738
  removeGridComponent,
271272
271739
  theme,
271273
- showAxisLines = false,
271740
+ showAxisLines = true,
271274
271741
  title: title2 = "Spatial",
271275
271742
  subtitle = "Powered by Neuroglancer",
271276
271743
  helpText = ViewHelpMapping.NEUROGLANCER,
271744
+ meshLoadProjectionScaleThreshold,
271277
271745
  // Note: this is a temporary mechanism
271278
271746
  // to pass an initial NG camera state.
271279
271747
  // Ideally, all camera state should be passed via
@@ -271284,6 +271752,32 @@ function NeuroglancerSubscriber(props) {
271284
271752
  } = props;
271285
271753
  const loaders = useLoaders();
271286
271754
  const mergeCoordination = useMergeCoordination();
271755
+ const { classes: classes2 } = useStyles$4();
271756
+ const initialRotationPushedRef = useRef(false);
271757
+ const getViewProjectionMatRef = useRef(null);
271758
+ const obsIdToMeshIdRef = useRef({});
271759
+ const meshIdToCellIdRef = useRef({});
271760
+ const cellIdToMeshIdRef = useRef({});
271761
+ const ngRotPushAtRef = useRef(0);
271762
+ const lastInteractionSource = useRef(null);
271763
+ const applyNgUpdateTimeoutRef = useRef(null);
271764
+ const lastNgPushOrientationRef = useRef(null);
271765
+ const initialRenderCalibratorRef = useRef(null);
271766
+ const translationOffsetRef = useRef([0, 0, 0]);
271767
+ const zoomRafRef = useRef(null);
271768
+ const lastNgQuatRef = useRef([0, 0, 0, 1]);
271769
+ const lastNgScaleRef = useRef(null);
271770
+ const annotationInfoRef = useRef(null);
271771
+ const annotationTransformRef = useRef(null);
271772
+ const visibleSegmentIdsRef = useRef(null);
271773
+ const chunkCacheRef = useRef(/* @__PURE__ */ new Map());
271774
+ const resizeObserverRef = useRef(null);
271775
+ const [isLayersLoaded, setIsLayersLoaded] = useState(false);
271776
+ const [isMeshLoading, setIsMeshLoading] = useState(false);
271777
+ const [annotationReady, setAnnotationReady] = useState(false);
271778
+ const [csvLoaded, setCsvLoaded] = useState(false);
271779
+ const updateVisibleSegmentsThrottledRef = useRef(null);
271780
+ const viewportSizeRef = useRef({ width: 0, height: 0 });
271287
271781
  const coordinationScopes = useCoordinationScopes(coordinationScopesRaw);
271288
271782
  const coordinationScopesBy = useCoordinationScopesBy(coordinationScopes, coordinationScopesByRaw);
271289
271783
  const [{
@@ -271315,6 +271809,19 @@ function NeuroglancerSubscriber(props) {
271315
271809
  setSpatialRotationOrbit: setRotationOrbit,
271316
271810
  setSpatialZoom: setZoom
271317
271811
  }] = useCoordination(COMPONENT_COORDINATION_TYPES[ViewType$1.NEUROGLANCER], coordinationScopes);
271812
+ const csvUrlRef = useRef(null);
271813
+ const csvUrl = useMemo$1(() => {
271814
+ if (csvUrlRef.current)
271815
+ return csvUrlRef.current;
271816
+ const obsSetsMap = loaders?.[dataset]?.loaders?.obsSets;
271817
+ if (!obsSetsMap)
271818
+ return null;
271819
+ const firstLoader = obsSetsMap.values().next().value;
271820
+ const url = firstLoader?.url ?? null;
271821
+ if (url)
271822
+ csvUrlRef.current = url;
271823
+ return url;
271824
+ }, [loaders, dataset]);
271318
271825
  const [ngWidth, ngHeight, containerRef] = useGridItemSize();
271319
271826
  const [segmentationLayerScopes, segmentationChannelScopesByLayer] = useMultiCoordinationScopesSecondaryNonNull(CoordinationType$1.SEGMENTATION_CHANNEL, CoordinationType$1.SEGMENTATION_LAYER, coordinationScopes, coordinationScopesBy);
271320
271827
  const pointLayerScopes = useMultiCoordinationScopesNonNull(CoordinationType$1.POINT_LAYER, coordinationScopes);
@@ -271343,7 +271850,9 @@ function NeuroglancerSubscriber(props) {
271343
271850
  CoordinationType$1.OBS_HIGHLIGHT,
271344
271851
  CoordinationType$1.TOOLTIPS_VISIBLE,
271345
271852
  CoordinationType$1.TOOLTIP_CROSSHAIRS_VISIBLE,
271346
- CoordinationType$1.LEGEND_VISIBLE
271853
+ CoordinationType$1.LEGEND_VISIBLE,
271854
+ CoordinationType$1.FEATURE_TYPE,
271855
+ CoordinationType$1.FEATURE_VALUE_TYPE
271347
271856
  ], coordinationScopes, coordinationScopesBy, CoordinationType$1.SEGMENTATION_LAYER, CoordinationType$1.SEGMENTATION_CHANNEL);
271348
271857
  const pointLayerCoordination = useComplexCoordination([
271349
271858
  CoordinationType$1.OBS_TYPE,
@@ -271360,7 +271869,10 @@ function NeuroglancerSubscriber(props) {
271360
271869
  CoordinationType$1.TOOLTIPS_VISIBLE,
271361
271870
  CoordinationType$1.TOOLTIP_CROSSHAIRS_VISIBLE,
271362
271871
  CoordinationType$1.LEGEND_VISIBLE,
271363
- CoordinationType$1.SPATIAL_POINT_STROKE_WIDTH
271872
+ CoordinationType$1.SPATIAL_POINT_STROKE_WIDTH,
271873
+ CoordinationType$1.OBS_SET_COLOR,
271874
+ CoordinationType$1.OBS_SET_SELECTION,
271875
+ CoordinationType$1.ADDITIONAL_OBS_SETS
271364
271876
  ], coordinationScopes, coordinationScopesBy, CoordinationType$1.POINT_LAYER);
271365
271877
  const [obsPointsData, obsPointsDataStatus, obsPointsUrls, obsPointsErrors] = useMultiObsPoints(coordinationScopes, coordinationScopesBy, loaders, dataset, mergeCoordination, uuid2);
271366
271878
  const [pointMultiIndicesData, pointMultiIndicesDataStatus, pointMultiIndicesDataErrors] = usePointMultiObsFeatureMatrixIndices(coordinationScopes, coordinationScopesBy, loaders, dataset);
@@ -271386,34 +271898,89 @@ function NeuroglancerSubscriber(props) {
271386
271898
  segmentationMultiFeatureSelectionStatus,
271387
271899
  segmentationMultiIndicesDataStatus
271388
271900
  ]);
271389
- const { classes: classes2 } = useStyles$4();
271901
+ const lastVitessceRotationRef = useRef({
271902
+ x: spatialRotationX,
271903
+ y: spatialRotationY,
271904
+ z: spatialRotationZ,
271905
+ orbit: spatialRotationOrbit
271906
+ });
271907
+ const prevCoordsRef = useRef({
271908
+ zoom: spatialZoom,
271909
+ rx: spatialRotationX,
271910
+ ry: spatialRotationY,
271911
+ rz: spatialRotationZ,
271912
+ orbit: spatialRotationOrbit,
271913
+ tx: spatialTargetX,
271914
+ ty: spatialTargetY
271915
+ });
271390
271916
  const segmentationColorMapping = useMemoCustomComparison(() => {
271391
271917
  const result = {};
271392
271918
  segmentationLayerScopes?.forEach((layerScope) => {
271393
271919
  result[layerScope] = {};
271394
271920
  segmentationChannelScopesByLayer?.[layerScope]?.forEach((channelScope) => {
271395
- const { obsSets: layerSets, obsIndex: layerIndex } = obsSegmentationsSetsData?.[layerScope]?.[channelScope] || {};
271396
- const { obsSetColor, obsColorEncoding, obsSetSelection, additionalObsSets, spatialChannelColor, spatialChannelOpacity } = segmentationChannelCoordination[0][layerScope][channelScope];
271921
+ const { obsSets: layerSets, obsIndex: layerIndexFromSets } = obsSegmentationsSetsData?.[layerScope]?.[channelScope] || {};
271922
+ const layerIndex = layerIndexFromSets ?? null;
271923
+ const { obsSetColor, obsColorEncoding, obsSetSelection, additionalObsSets, spatialChannelColor, spatialChannelOpacity, featureValueColormap, featureValueColormapRange } = segmentationChannelCoordination[0][layerScope][channelScope];
271397
271924
  if (obsColorEncoding === "spatialChannelColor") {
271398
- if (layerIndex && spatialChannelColor) {
271925
+ if (spatialChannelColor) {
271399
271926
  const hex2 = rgbToHex$1(spatialChannelColor);
271400
271927
  const ngCellColors = {};
271401
- if (obsSetSelection?.length > 0) {
271402
- const mergedCellSets = mergeObsSets(layerSets, additionalObsSets);
271403
- const selectedIds = /* @__PURE__ */ new Set();
271404
- obsSetSelection.forEach((setPath) => {
271405
- const rootNode = mergedCellSets?.tree?.find((n3) => n3.name === setPath[0]);
271406
- const leafNode = setPath.length > 1 ? rootNode?.children?.find((n3) => n3.name === setPath[1]) : rootNode;
271407
- leafNode?.set?.forEach(([id2]) => selectedIds.add(String(id2)));
271408
- });
271409
- layerIndex.forEach((id2) => {
271410
- if (selectedIds.has(String(id2))) {
271928
+ if (layerIndex) {
271929
+ if (obsSetSelection?.length > 0) {
271930
+ const mergedCellSets = mergeObsSets(layerSets, additionalObsSets);
271931
+ const selectedIds = /* @__PURE__ */ new Set();
271932
+ obsSetSelection.forEach((setPath) => {
271933
+ const rootNode = mergedCellSets?.tree?.find((n3) => n3.name === setPath[0]);
271934
+ const leafNode = setPath.length > 1 ? rootNode?.children?.find((n3) => n3.name === setPath[1]) : rootNode;
271935
+ leafNode?.set?.forEach(([id2]) => selectedIds.add(String(id2)));
271936
+ });
271937
+ layerIndex.forEach((id2) => {
271938
+ if (selectedIds.has(String(id2))) {
271939
+ ngCellColors[id2] = hex2;
271940
+ }
271941
+ });
271942
+ } else {
271943
+ layerIndex.forEach((id2) => {
271411
271944
  ngCellColors[id2] = hex2;
271412
- }
271413
- });
271945
+ });
271946
+ }
271414
271947
  }
271415
- result[layerScope][channelScope] = ngCellColors;
271948
+ result[layerScope][channelScope] = remapCellColors(ngCellColors, cellIdToMeshIdRef);
271949
+ result[layerScope].opacity = spatialChannelOpacity ?? 1;
271950
+ result[layerScope].defaultColor = hex2;
271951
+ }
271952
+ } else if (obsColorEncoding === "geneSelection") {
271953
+ const instanceObsIndex = obsSegmentationsSetsData?.[layerScope]?.[channelScope]?.obsIndex;
271954
+ const matrixObsIndex = segmentationMultiIndicesData?.[layerScope]?.[channelScope]?.obsIndex;
271955
+ const expressionData = segmentationMultiExpressionNormData?.[layerScope]?.[channelScope];
271956
+ if (instanceObsIndex && matrixObsIndex && expressionData?.[0]) {
271957
+ const matrixIndexMap = new Map(matrixObsIndex.map((key2, i2) => {
271958
+ const normalizedKey = key2.replace(/^.*_/, "");
271959
+ return [normalizedKey, i2];
271960
+ }));
271961
+ const toMatrixIndex = instanceObsIndex.map((key2) => matrixIndexMap.get(String(key2)));
271962
+ const [low, high] = featureValueColormapRange ?? [0, 1];
271963
+ const ngCellColors = {};
271964
+ instanceObsIndex.forEach((id2, i2) => {
271965
+ const rowIndex = toMatrixIndex[i2];
271966
+ const rawVal = expressionData[0][rowIndex] ?? 0;
271967
+ const t4 = rawVal / 255;
271968
+ const tScaled = (t4 - low) / Math.max(high - low, 1e-4);
271969
+ const tClamped = Math.max(0, Math.min(1, tScaled));
271970
+ const color2 = applyColormap(featureValueColormap ?? "viridis", tClamped);
271971
+ ngCellColors[id2] = rgbToHex$1(color2);
271972
+ });
271973
+ result[layerScope][channelScope] = remapCellColors(ngCellColors, cellIdToMeshIdRef);
271974
+ result[layerScope].opacity = spatialChannelOpacity ?? 1;
271975
+ } else if (instanceObsIndex) {
271976
+ const fallbackColor = spatialChannelColor ? rgbToHex$1(spatialChannelColor) : GREY_HEX;
271977
+ const ngCellColors = {};
271978
+ instanceObsIndex.forEach((id2) => {
271979
+ ngCellColors[id2] = fallbackColor;
271980
+ });
271981
+ result[layerScope][channelScope] = remapCellColors(ngCellColors, cellIdToMeshIdRef);
271416
271982
  result[layerScope].opacity = spatialChannelOpacity ?? 1;
271983
+ result[layerScope].defaultColor = fallbackColor;
271417
271984
  }
271418
271985
  } else if (layerSets && layerIndex) {
271419
271986
  const mergedCellSets = mergeObsSets(layerSets, additionalObsSets);
@@ -271425,10 +271992,10 @@ function NeuroglancerSubscriber(props) {
271425
271992
  theme
271426
271993
  });
271427
271994
  const ngCellColors = {};
271428
- cellColors.forEach((color2, i2) => {
271429
- ngCellColors[i2] = rgbToHex$1(color2);
271995
+ cellColors.forEach((color2, id2) => {
271996
+ ngCellColors[id2] = rgbToHex$1(color2);
271430
271997
  });
271431
- result[layerScope][channelScope] = ngCellColors;
271998
+ result[layerScope][channelScope] = remapCellColors(ngCellColors, cellIdToMeshIdRef);
271432
271999
  result[layerScope].opacity = spatialChannelOpacity ?? 1;
271433
272000
  }
271434
272001
  });
@@ -271441,14 +272008,201 @@ function NeuroglancerSubscriber(props) {
271441
272008
  segmentationChannelScopesByLayer,
271442
272009
  obsSegmentationsSetsData,
271443
272010
  segmentationChannelCoordination,
271444
- theme
272011
+ theme,
272012
+ segmentationMultiExpressionNormData,
272013
+ segmentationMultiIndicesData,
272014
+ csvLoaded
271445
272015
  }, customIsEqualForCellColors);
271446
- const initalViewerState = useNeuroglancerViewerState(theme, showAxisLines, segmentationLayerScopes, segmentationChannelScopesByLayer, segmentationLayerCoordination, segmentationChannelCoordination, obsSegmentationsUrls, obsSegmentationsData, pointLayerScopes, pointLayerCoordination, obsPointsUrls, obsPointsData, pointMultiIndicesData);
272016
+ const initialViewerState = useNeuroglancerViewerState(theme, showAxisLines, segmentationLayerScopes, segmentationChannelScopesByLayer, segmentationLayerCoordination, segmentationChannelCoordination, obsSegmentationsUrls, obsSegmentationsData, pointLayerScopes, pointLayerCoordination, obsPointsUrls, obsPointsData, pointMultiIndicesData);
271447
272017
  const [latestViewerStateIteration, incrementLatestViewerStateIteration] = useReducer((x2) => x2 + 1, 0);
271448
272018
  const latestViewerStateRef = useRef({
271449
- ...initalViewerState,
272019
+ ...initialViewerState,
271450
272020
  ...initialNgCameraState ?? {}
271451
272021
  });
272022
+ useEffect(() => {
272023
+ if (!csvUrl)
272024
+ return;
272025
+ fetch(csvUrl).then((r3) => r3.text()).then((text2) => {
272026
+ const lines = text2.split("\n");
272027
+ const header = lines[0].split(",");
272028
+ const meshIdIdx = header.indexOf("MeshID");
272029
+ const cellIdIdx = header.indexOf("CellID");
272030
+ const meshMap = {};
272031
+ const cellMap = {};
272032
+ lines.slice(1).forEach((l2) => {
272033
+ const cols = l2.split(",");
272034
+ const meshId = cols[meshIdIdx]?.trim();
272035
+ const cellId = cols[cellIdIdx]?.trim();
272036
+ if (meshId && cellId) {
272037
+ meshMap[meshId] = cellId;
272038
+ cellMap[cellId] = meshId;
272039
+ }
272040
+ });
272041
+ meshIdToCellIdRef.current = meshMap;
272042
+ cellIdToMeshIdRef.current = cellMap;
272043
+ setCsvLoaded(true);
272044
+ });
272045
+ }, [csvUrl]);
272046
+ useEffect(() => {
272047
+ chunkCacheRef.current.clear();
272048
+ }, [obsPointsData]);
272049
+ useEffect(() => {
272050
+ if (ngWidth && ngHeight) {
272051
+ viewportSizeRef.current = { width: ngWidth, height: ngHeight };
272052
+ }
272053
+ }, [ngWidth, ngHeight]);
272054
+ const setContainerNode = useCallback((node2) => {
272055
+ containerRef.current = node2;
272056
+ if (resizeObserverRef.current) {
272057
+ resizeObserverRef.current.disconnect();
272058
+ resizeObserverRef.current = null;
272059
+ }
272060
+ if (node2) {
272061
+ const rect2 = node2.getBoundingClientRect();
272062
+ if (rect2.width && rect2.height) {
272063
+ viewportSizeRef.current = { width: rect2.width, height: rect2.height };
272064
+ }
272065
+ const observer2 = new ResizeObserver((entries2) => {
272066
+ const entry2 = entries2[0];
272067
+ if (!entry2)
272068
+ return;
272069
+ const { width: width2, height: height2 } = entry2.contentRect;
272070
+ if (width2 && height2) {
272071
+ viewportSizeRef.current = { width: width2, height: height2 };
272072
+ }
272073
+ });
272074
+ observer2.observe(node2);
272075
+ resizeObserverRef.current = observer2;
272076
+ }
272077
+ }, []);
272078
+ const updateVisibleSegments = useCallback(async () => {
272079
+ if (!annotationInfoRef.current)
272080
+ return;
272081
+ if (!annotationTransformRef.current)
272082
+ return;
272083
+ if (!segmentationLayerScopes?.length)
272084
+ return;
272085
+ if (!pointLayerScopes?.length)
272086
+ return;
272087
+ const { position: position2, projectionScale } = latestViewerStateRef.current;
272088
+ if (!position2 || !projectionScale)
272089
+ return;
272090
+ const maxProjectionScale = meshLoadProjectionScaleThreshold ?? MESH_LOAD_THRESHOLD;
272091
+ if (projectionScale > maxProjectionScale) {
272092
+ if (visibleSegmentIdsRef.current?.length !== 0) {
272093
+ visibleSegmentIdsRef.current = [];
272094
+ incrementLatestViewerStateIteration();
272095
+ setIsMeshLoading(false);
272096
+ }
272097
+ return;
272098
+ }
272099
+ const { width: width2, height: height2 } = viewportSizeRef.current;
272100
+ if (!width2 || !height2)
272101
+ return;
272102
+ const transform3 = annotationTransformRef.current;
272103
+ const info2 = annotationInfoRef.current;
272104
+ const cellsUrl2 = info2.url;
272105
+ const allLevelCoords = info2.spatial.flatMap((level) => {
272106
+ const [gx, gy, gz] = level.grid_shape;
272107
+ const coords = [];
272108
+ for (let cx = 0; cx < gx; cx++) {
272109
+ for (let cy = 0; cy < gy; cy++) {
272110
+ for (let cz = 0; cz < gz; cz++) {
272111
+ coords.push({ level: level.key, cx, cy, cz });
272112
+ }
272113
+ }
272114
+ }
272115
+ return coords;
272116
+ });
272117
+ const fetchChunkWithPositions = async ({ level, cx, cy, cz }) => {
272118
+ const { serializers, serializer: defaultSerializer } = annotationTransformRef.current;
272119
+ const serializer2 = serializers?.[0] ?? defaultSerializer;
272120
+ if (!serializer2)
272121
+ return [];
272122
+ const cacheKey = `${cellsUrl2}/${level}/${cx}_${cy}_${cz}`;
272123
+ if (chunkCacheRef.current.has(cacheKey)) {
272124
+ return chunkCacheRef.current.get(cacheKey);
272125
+ }
272126
+ try {
272127
+ const res = await fetch(cacheKey);
272128
+ if (!res.ok) {
272129
+ chunkCacheRef.current.set(cacheKey, []);
272130
+ return [];
272131
+ }
272132
+ const buffer2 = await res.arrayBuffer();
272133
+ const entries2 = parseAnnotationChunkSegmentsWithPositions(buffer2, serializer2);
272134
+ chunkCacheRef.current.set(cacheKey, entries2);
272135
+ return entries2;
272136
+ } catch (e3) {
272137
+ chunkCacheRef.current.set(cacheKey, []);
272138
+ return [];
272139
+ }
272140
+ };
272141
+ try {
272142
+ const results = await Promise.all(allLevelCoords.map(fetchChunkWithPositions));
272143
+ const seenIds = /* @__PURE__ */ new Set();
272144
+ const allEntries = results.flat().filter(({ id: id2 }) => {
272145
+ if (seenIds.has(id2))
272146
+ return false;
272147
+ seenIds.add(id2);
272148
+ return true;
272149
+ });
272150
+ const obsIdToMeshId = {};
272151
+ allEntries.forEach(({ id: id2, obsId }) => {
272152
+ obsIdToMeshId[obsId] = id2;
272153
+ });
272154
+ obsIdToMeshIdRef.current = obsIdToMeshId;
272155
+ const mat = getViewProjectionMatRef.current?.();
272156
+ let visibleIds;
272157
+ if (!mat) {
272158
+ console.warn("No viewProjectionMatrix, loading all");
272159
+ visibleIds = [...new Set(allEntries.map(({ id: id2 }) => id2))];
272160
+ } else {
272161
+ const margin2 = Math.max(width2, height2) * 0.5;
272162
+ visibleIds = [...new Set(allEntries.filter(({ x: x2, y: y2, z: z2 }) => {
272163
+ const vx = x2 / transform3.x;
272164
+ const vy = y2 / transform3.y;
272165
+ const vz = (z2 || 0) / transform3.z;
272166
+ const cx = mat[0] * vx + mat[4] * vy + mat[8] * vz + mat[12];
272167
+ const cy = mat[1] * vx + mat[5] * vy + mat[9] * vz + mat[13];
272168
+ const cw2 = mat[3] * vx + mat[7] * vy + mat[11] * vz + mat[15];
272169
+ const screenX = (cx / cw2 + 1) * 0.5 * width2;
272170
+ const screenY = (1 - cy / cw2) * 0.5 * height2;
272171
+ return screenX >= -margin2 && screenX <= width2 + margin2 && screenY >= -margin2 && screenY <= height2 + margin2;
272172
+ }).map(({ id: id2 }) => id2))];
272173
+ }
272174
+ if (visibleIds.length === 0) {
272175
+ if (visibleSegmentIdsRef.current?.length !== 0) {
272176
+ visibleSegmentIdsRef.current = [];
272177
+ incrementLatestViewerStateIteration();
272178
+ }
272179
+ setIsMeshLoading(false);
272180
+ return;
272181
+ }
272182
+ const prevIds = new Set(visibleSegmentIdsRef.current ?? []);
272183
+ const addedIdsCount = visibleIds.filter((id2) => !prevIds.has(id2)).length;
272184
+ const hasSignificantChange = addedIdsCount > 20;
272185
+ if (hasSignificantChange && visibleIds.length > 0) {
272186
+ setIsMeshLoading(true);
272187
+ }
272188
+ const prevSorted = [...visibleSegmentIdsRef.current ?? []].sort().join(",");
272189
+ const nextSorted = [...visibleIds].sort().join(",");
272190
+ if (prevSorted !== nextSorted) {
272191
+ visibleSegmentIdsRef.current = visibleIds;
272192
+ incrementLatestViewerStateIteration();
272193
+ }
272194
+ if (hasSignificantChange) {
272195
+ setTimeout(() => setIsMeshLoading(false), MESH_LOADING_OVERLAY_TIMEOUT);
272196
+ }
272197
+ } catch (e3) {
272198
+ console.warn("[updateVisibleSegments] error:", e3);
272199
+ setIsMeshLoading(false);
272200
+ }
272201
+ }, [segmentationLayerScopes, pointLayerScopes, obsPointsData, meshLoadProjectionScaleThreshold]);
272202
+ useEffect(() => {
272203
+ updateVisibleSegmentsThrottledRef.current = throttle$1(updateVisibleSegments, 500);
272204
+ return () => updateVisibleSegmentsThrottledRef.current?.cancel();
272205
+ }, [updateVisibleSegments]);
271452
272206
  useEffect(() => {
271453
272207
  const prevNgCameraState = {
271454
272208
  position: latestViewerStateRef.current.position,
@@ -271456,37 +272210,50 @@ function NeuroglancerSubscriber(props) {
271456
272210
  projectionScale: latestViewerStateRef.current.projectionScale
271457
272211
  };
271458
272212
  latestViewerStateRef.current = {
271459
- ...initalViewerState,
272213
+ ...initialViewerState,
271460
272214
  ...prevNgCameraState
271461
272215
  };
271462
272216
  incrementLatestViewerStateIteration();
271463
- }, [initalViewerState]);
271464
- const initialRotationPushedRef = useRef(false);
271465
- const ngRotPushAtRef = useRef(0);
271466
- const lastInteractionSource = useRef(null);
271467
- const applyNgUpdateTimeoutRef = useRef(null);
271468
- const lastNgPushOrientationRef = useRef(null);
271469
- const initialRenderCalibratorRef = useRef(null);
271470
- const translationOffsetRef = useRef([0, 0, 0]);
271471
- const zoomRafRef = useRef(null);
271472
- const lastNgQuatRef = useRef([0, 0, 0, 1]);
271473
- const lastNgScaleRef = useRef(null);
271474
- const lastVitessceRotationRef = useRef({
271475
- x: spatialRotationX,
271476
- y: spatialRotationY,
271477
- z: spatialRotationZ,
271478
- orbit: spatialRotationOrbit
271479
- });
271480
- const [isLayersLoaded, setIsLayersLoaded] = useState(false);
271481
- const prevCoordsRef = useRef({
271482
- zoom: spatialZoom,
271483
- rx: spatialRotationX,
271484
- ry: spatialRotationY,
271485
- rz: spatialRotationZ,
271486
- orbit: spatialRotationOrbit,
271487
- tx: spatialTargetX,
271488
- ty: spatialTargetY
271489
- });
272217
+ }, [initialViewerState]);
272218
+ const cellsUrl = useMemo$1(() => {
272219
+ const firstScope = pointLayerScopes?.[0];
272220
+ return obsPointsUrls?.[firstScope]?.[0]?.url ?? null;
272221
+ }, [pointLayerScopes, obsPointsUrls]);
272222
+ const hasMatchingAnnotationSource = useMemo$1(() => {
272223
+ if (!cellsUrl)
272224
+ return false;
272225
+ const firstPointScope = pointLayerScopes?.[0];
272226
+ return pointsHaveMatchingSegmentation({
272227
+ pointObsType: pointLayerCoordination[0]?.[firstPointScope]?.obsType,
272228
+ segmentationLayerScopes,
272229
+ segmentationChannelScopesByLayer,
272230
+ segmentationChannelCoordination
272231
+ });
272232
+ }, [cellsUrl, pointLayerScopes, segmentationLayerScopes, segmentationChannelScopesByLayer]);
272233
+ useEffect(() => {
272234
+ if (!cellsUrl)
272235
+ return;
272236
+ fetch(`${cellsUrl}/info`).then((r3) => r3.json()).then((info2) => {
272237
+ const infoWithUrl = {
272238
+ ...info2,
272239
+ url: cellsUrl
272240
+ };
272241
+ annotationInfoRef.current = infoWithUrl;
272242
+ if (annotationTransformRef.current)
272243
+ setAnnotationReady(true);
272244
+ }).catch((err2) => console.error("failed to fetch annotation info:", err2));
272245
+ }, [cellsUrl]);
272246
+ useEffect(() => {
272247
+ if (annotationReady) {
272248
+ setIsLayersLoaded(true);
272249
+ updateVisibleSegments();
272250
+ }
272251
+ }, [annotationReady]);
272252
+ const onAnnotationSourceReady = useCallback((transform3) => {
272253
+ annotationTransformRef.current = transform3;
272254
+ if (annotationInfoRef.current)
272255
+ setAnnotationReady(true);
272256
+ }, []);
271490
272257
  const handleStateUpdate = useCallback((newState) => {
271491
272258
  lastInteractionSource.current = LAST_INTERACTION_SOURCE.neuroglancer;
271492
272259
  const { projectionScale, projectionOrientation, position: position2 } = newState;
@@ -271519,6 +272286,7 @@ function NeuroglancerSubscriber(props) {
271519
272286
  setZoom(vitZoomFromNg);
271520
272287
  zoomRafRef.current = null;
271521
272288
  });
272289
+ updateVisibleSegments();
271522
272290
  }
271523
272291
  lastNgScaleRef.current = projectionScale;
271524
272292
  }
@@ -271559,7 +272327,8 @@ function NeuroglancerSubscriber(props) {
271559
272327
  projectionScale,
271560
272328
  position: position2
271561
272329
  };
271562
- }, []);
272330
+ updateVisibleSegmentsThrottledRef.current?.();
272331
+ }, [updateVisibleSegmentsThrottledRef]);
271563
272332
  const onSegmentClick = useCallback((value2) => {
271564
272333
  if (value2) {
271565
272334
  const id2 = String(value2);
@@ -271584,11 +272353,18 @@ function NeuroglancerSubscriber(props) {
271584
272353
  const channelScope = segmentationChannelScopesByLayer?.[layerScope]?.[0];
271585
272354
  result[layerScope] = {
271586
272355
  colors: segmentationColorMapping?.[layerScope]?.[channelScope] ?? {},
271587
- opacity: segmentationColorMapping?.[layerScope]?.opacity ?? 1
272356
+ opacity: segmentationColorMapping?.[layerScope]?.opacity ?? 1,
272357
+ defaultColor: segmentationColorMapping?.[layerScope]?.defaultColor ?? null
271588
272358
  };
271589
272359
  });
271590
272360
  return result;
271591
272361
  }, [segmentationColorMapping, segmentationLayerScopes, segmentationChannelScopesByLayer]);
272362
+ useEffect(() => {
272363
+ if (!hasMatchingAnnotationSource && isReady && !segmentationLayerScopes?.length) {
272364
+ setIsLayersLoaded(true);
272365
+ }
272366
+ }, [hasMatchingAnnotationSource, isReady, segmentationLayerScopes]);
272367
+ const meshOpacity = hasMatchingAnnotationSource ? MESH_OPACITY : void 0;
271592
272368
  const derivedViewerState = useMemo$1(() => {
271593
272369
  const { current: current2 } = latestViewerStateRef;
271594
272370
  if (current2.layers.length <= 0) {
@@ -271647,13 +272423,33 @@ function NeuroglancerSubscriber(props) {
271647
272423
  lastInteractionSource.current = null;
271648
272424
  }
271649
272425
  const updatedLayers = current2?.layers?.map((layer, idx) => {
271650
- const layerScope = segmentationLayerScopes?.[idx];
272426
+ if (layer.type !== "segmentation")
272427
+ return layer;
272428
+ const layerScope = segmentationLayerScopes?.find((scope) => layer.name?.includes(scope));
272429
+ if (!layerScope)
272430
+ return layer;
271651
272431
  const layerColorMapping = cellColorMappingByLayer?.[layerScope]?.colors ?? {};
271652
- const layerSegments = Object.keys(layerColorMapping);
272432
+ const defaultColor = cellColorMappingByLayer?.[layerScope]?.defaultColor;
272433
+ let segments2 = [];
272434
+ if (hasMatchingAnnotationSource) {
272435
+ segments2 = visibleSegmentIdsRef.current ?? [];
272436
+ } else if (Object.keys(layerColorMapping).length > 0) {
272437
+ segments2 = Object.keys(layerColorMapping);
272438
+ }
272439
+ let derivedSegmentColors = {};
272440
+ if (hasMatchingAnnotationSource && segments2.length > 0) {
272441
+ segments2.forEach((meshId) => {
272442
+ const color2 = layerColorMapping[meshId] || defaultColor;
272443
+ if (color2)
272444
+ derivedSegmentColors[meshId] = color2;
272445
+ });
272446
+ } else if (!hasMatchingAnnotationSource) {
272447
+ derivedSegmentColors = layerColorMapping;
272448
+ }
271653
272449
  return {
271654
272450
  ...layer,
271655
- segments: layerSegments,
271656
- segmentColors: layerColorMapping,
272451
+ segments: segments2,
272452
+ segmentColors: derivedSegmentColors,
271657
272453
  objectAlpha: cellColorMappingByLayer?.[layerScope]?.opacity ?? 1
271658
272454
  };
271659
272455
  }) ?? [];
@@ -271683,17 +272479,21 @@ function NeuroglancerSubscriber(props) {
271683
272479
  spatialRotationZ,
271684
272480
  spatialTargetX,
271685
272481
  spatialTargetY,
271686
- initalViewerState,
271687
- latestViewerStateIteration
272482
+ initialViewerState,
272483
+ latestViewerStateIteration,
272484
+ hasMatchingAnnotationSource
271688
272485
  ]);
271689
272486
  const onSegmentHighlight = useCallback((obsId) => {
271690
272487
  setCellHighlight(String(obsId));
271691
272488
  }, [setCellHighlight]);
271692
272489
  const handleLayerLoadingChange = useCallback((isLoaded) => {
271693
- setIsLayersLoaded(isLoaded);
271694
- }, []);
272490
+ if (!isLayersLoaded && isLoaded) {
272491
+ setIsLayersLoaded(true);
272492
+ }
272493
+ }, [isLayersLoaded]);
271695
272494
  const hasLayers = derivedViewerState?.layers?.length > 0;
271696
- return jsx$1(TitleInfo, { title: title2, info: subtitle, helpText, isSpatial: true, theme, closeButtonVisible, downloadButtonVisible, removeGridComponent, isReady: isReady && isLayersLoaded, errors, withPadding: false, guideUrl: GUIDE_URL, children: hasLayers ? jsxs("div", { style: { position: "relative", width: "100%", height: "100%" }, ref: containerRef, children: [jsx$1("div", { style: { position: "absolute", top: 0, right: 0, zIndex: 50 }, children: jsx$1(MultiLegend, {
272495
+ const showPointsLegend = !hasMatchingAnnotationSource;
272496
+ return jsx$1(TitleInfo, { title: title2, info: subtitle, helpText, isSpatial: true, theme, closeButtonVisible, downloadButtonVisible, removeGridComponent, isReady: isReady && isLayersLoaded, errors, withPadding: false, guideUrl: GUIDE_URL, children: hasLayers ? jsxs("div", { style: { position: "relative", width: "100%", height: "100%" }, ref: setContainerNode, children: [jsx$1("div", { style: { position: "absolute", top: 0, right: 0, zIndex: 50 }, children: jsx$1(MultiLegend, {
271697
272497
  theme: "dark",
271698
272498
  maxHeight: ngHeight,
271699
272499
  // Segmentations
@@ -271702,10 +272502,12 @@ function NeuroglancerSubscriber(props) {
271702
272502
  segmentationChannelScopesByLayer,
271703
272503
  segmentationChannelCoordination,
271704
272504
  // Points
271705
- pointLayerScopes,
272505
+ pointLayerScopes: showPointsLegend ? pointLayerScopes : void 0,
271706
272506
  pointLayerCoordination,
271707
272507
  pointMultiIndicesData
271708
- }) }), jsx$1(NeuroglancerComp, { classes: classes2, onSegmentClick, onSelectHoveredCoords: onSegmentHighlight, viewerState: derivedViewerState, cellColorMapping: cellColorMappingByLayer, setViewerState: handleStateUpdate, onLayerLoadingChange: handleLayerLoadingChange })] }) : null });
272508
+ }) }), isMeshLoading ? jsx$1(Chip, { label: "Loading meshes...", size: "small", color: "primary", className: classes2.meshLoadingIndicator }) : null, jsx$1(NeuroglancerComp, { classes: classes2, onSegmentClick, onSelectHoveredCoords: onSegmentHighlight, viewerState: derivedViewerState, cellColorMapping: cellColorMappingByLayer, setViewerState: handleStateUpdate, onLayerLoadingChange: handleLayerLoadingChange, onAnnotationSourceReady, onViewerReady: (getFn) => {
272509
+ getViewProjectionMatRef.current = getFn;
272510
+ }, getMeshIdToCellId: (meshId) => meshIdToCellIdRef.current[meshId], cellsUrl, meshOpacity })] }) : null });
271709
272511
  }
271710
272512
  const FEATURE_AGGREGATION_STRATEGIES = ["first", "last", "sum", "mean"];
271711
272513
  function CellSetExpressionPlotOptions(props) {
@@ -273544,7 +274346,7 @@ function useFilteredVolcanoData(props) {
273544
274346
  const rawSampleSetPathB = coordinationValues.sampleSetFilter[1];
273545
274347
  const sampleSetPathB = [...rawSampleSetPathB];
273546
274348
  sampleSetPathB[0] = sampleSetsColumnNameMappingReversed[rawSampleSetPathB[0]];
273547
- if (isEqual$3(sampleSetPathA, sampleSetSelection[1]) && isEqual$3(sampleSetPathB, sampleSetSelection[0])) {
274349
+ if (isEqual$3(sampleSetPathA, sampleSetSelection[0]) && isEqual$3(sampleSetPathB, sampleSetSelection[1])) {
273548
274350
  shouldSwapFoldChangeDirection = true;
273549
274351
  }
273550
274352
  }
@@ -324203,7 +325005,7 @@ const VEGA_THEMES = {
324203
325005
  };
324204
325006
  const DATASET_NAME = "table";
324205
325007
  function VegaGlobalStyles() {
324206
- const { theme } = useStyles$x();
325008
+ const { theme } = useStyles$y();
324207
325009
  return jsx$1(GlobalStyles$3, { styles: {
324208
325010
  "#vg-tooltip-element.vg-tooltip.custom-theme": {
324209
325011
  boxShadow: "0px 2px 4px -1px rgba(0,0,0,0.2),0px 4px 5px 0px rgba(0,0,0,0.14),0px 1px 10px 0px rgba(0,0,0,0.12)",
@@ -324237,7 +325039,7 @@ function renderTooltipContents(tooltipText) {
324237
325039
  }
324238
325040
  function VegaPlot(props) {
324239
325041
  const { spec: partialSpec, data: data2, getTooltipText, signalListeners, renderer = "svg", onNewView } = props;
324240
- const { classes: tooltipClasses2 } = useStyles$s();
325042
+ const { classes: tooltipClasses2 } = useStyles$t();
324241
325043
  const tooltipHandler = useMemo$1(() => {
324242
325044
  if (typeof getTooltipText === "function") {
324243
325045
  const tooltipConfig = {
@@ -325298,6 +326100,10 @@ function TreemapSubscriber(props) {
325298
326100
  }, [setObsSetSelection]);
325299
326101
  return jsxs(TitleInfo, { title: `Treemap of ${capitalize$3(pluralize(obsType, 2))}`, info: `${commaNumber(selectedObsCount)} ${pluralize(obsType, selectedObsCount)} from ${commaNumber(selectedSampleCount)} ${pluralize(sampleType, selectedSampleCount)}`, removeGridComponent, urls: urls2, theme, isReady, helpText, errors, withPadding: false, options: jsx$1(TreemapOptions, { obsType, sampleType, obsColorEncoding, setObsColorEncoding, hierarchyLevels: hierarchyLevels || DEFAULT_HIERARCHY_LEVELS, setHierarchyLevels }), children: [jsx$1("div", { ref: containerRef, className: classes2.vegaContainer, children: jsx$1(Treemap, { obsCounts, sampleCounts, obsColorEncoding, hierarchyLevels: hierarchyLevels || DEFAULT_HIERARCHY_LEVELS, theme, width: width2, height: Math.max(height2 * (selectedObsCount / totalObsCount), 40), obsType, sampleType, obsSetColor, sampleSetColor, obsSetSelection, sampleSetSelection, onNodeClick }) }), jsx$1("div", { style: { position: "absolute", right: "2px", bottom: "2px", fontSize: "10px" }, children: unselectedObsCount > 0 ? jsx$1("span", { children: `${commaNumber(unselectedObsCount)} ${pluralize(obsType, unselectedObsCount)} from ${commaNumber(unselectedSampleCount)} ${pluralize(sampleType, unselectedSampleCount)} currently omitted` }) : null })] });
325300
326102
  }
326103
+ const GOLDEN_RATIO = 0.6180339887498948;
326104
+ function topJitter(index2, rangePixels) {
326105
+ return index2 * GOLDEN_RATIO % 1 * rangePixels;
326106
+ }
325301
326107
  function VolcanoPlot(props) {
325302
326108
  const { theme, width: width2, height: height2, obsType, featureType, obsSetsColumnNameMappingReversed, sampleSetsColumnNameMappingReversed, sampleSetSelection, obsSetSelection, obsSetColor, sampleSetColor, data: data2, marginTop = 5, marginRight = 5, marginLeft = 50, marginBottom = 50, onFeatureClick, featurePointSignificanceThreshold, featurePointFoldChangeThreshold, featureLabelSignificanceThreshold, featureLabelFoldChangeThreshold } = props;
325303
326109
  const svgRef = useRef();
@@ -325359,10 +326165,10 @@ function VolcanoPlot(props) {
325359
326165
  const obsSetPath2 = [...rawObsSetPath];
325360
326166
  obsSetPath2[0] = obsSetsColumnNameMappingReversed[rawObsSetPath[0]];
325361
326167
  const color2 = obsSetColorScale(obsSetPath2);
325362
- obsSetG.append("g").selectAll("circle").data(filteredDf).join("circle").attr("cx", (d) => xScale(d.logFoldChange)).attr("cy", (d) => yScale(d.minusLog10p)).attr("r", 3).attr("opacity", 0.5).attr("fill", color2).on("click", (event2, d) => {
326168
+ obsSetG.append("g").selectAll("circle").data(filteredDf).join("circle").attr("cx", (d) => xScale(d.logFoldChange)).attr("cy", (d, i2) => yScale(d.minusLog10p) + (!Number.isFinite(d.minusLog10p) ? topJitter(i2, 20) : 0)).attr("r", 3).attr("opacity", 0.5).attr("fill", color2).on("click", (event2, d) => {
325363
326169
  onFeatureClick(d.featureId);
325364
326170
  });
325365
- const textElements = obsSetG.append("g").selectAll("text").data(filteredDf).join("text").text((d) => d.featureId).attr("text-anchor", (d) => d.logFoldChange < 0 ? "end" : "start").attr("x", (d) => xScale(d.logFoldChange)).attr("y", (d) => yScale(d.minusLog10p)).style("display", (d) => Math.abs(d.logFoldChange) < (featureLabelFoldChangeThreshold ?? 5) || d.featureSignificance >= (featureLabelSignificanceThreshold ?? 0.01) ? "none" : void 0).attr("fill", color2).on("click", (event2, d) => {
326171
+ const textElements = obsSetG.append("g").selectAll("text").data(filteredDf).join("text").text((d) => d.featureId).attr("text-anchor", (d) => d.logFoldChange < 0 ? "end" : "start").attr("x", (d) => xScale(d.logFoldChange)).attr("y", (d, i2) => yScale(d.minusLog10p) + (!Number.isFinite(d.minusLog10p) ? topJitter(i2, 20) : 0)).style("display", (d) => Math.abs(d.logFoldChange) < (featureLabelFoldChangeThreshold ?? 5) || d.featureSignificance >= (featureLabelSignificanceThreshold ?? 0.01) ? "none" : void 0).attr("fill", color2).on("click", (event2, d) => {
325366
326172
  onFeatureClick(d.featureId);
325367
326173
  });
325368
326174
  textElements.append("title").text((d) => `${featureType}: ${d.featureId}
@@ -349423,6 +350229,7 @@ const baseCoordinationTypes = [
349423
350229
  new PluginCoordinationType(CoordinationType$1.SPATIAL_CHANNEL_LABELS_VISIBLE, true, z.boolean()),
349424
350230
  new PluginCoordinationType(CoordinationType$1.SPATIAL_CHANNEL_LABELS_ORIENTATION, "vertical", z.enum(["vertical", "horizontal"])),
349425
350231
  new PluginCoordinationType(CoordinationType$1.SPATIAL_CHANNEL_LABEL_SIZE, 14, z.number()),
350232
+ new PluginCoordinationType(CoordinationType$1.SPATIAL_CHANNELS_SORT_ORDER, "original", z.enum(["original", "alphabetical"])),
349426
350233
  new PluginCoordinationType(CoordinationType$1.SAMPLE_TYPE, "sample", z.string().nullable()),
349427
350234
  // TODO: remove one array level and use multi-coordination for sampleSetSelection?
349428
350235
  new PluginCoordinationType(CoordinationType$1.SAMPLE_FILTER, null, z.array(z.string()).nullable()),
@@ -349522,7 +350329,7 @@ function Vitessce(props) {
349522
350329
  );
349523
350330
  }
349524
350331
  export {
349525
- diffCameraState as $,
350332
+ GREY_HEX as $,
349526
350333
  require_descriptors as A,
349527
350334
  BaseDecoder as B,
349528
350335
  CoordinationType$1 as C,
@@ -349551,13 +350358,14 @@ export {
349551
350358
  require_redefine as Z,
349552
350359
  require_objectAssign as _,
349553
350360
  getDefaultExportFromCjs$1 as a,
349554
- Vitessce as a0,
349555
- PluginFileType as a1,
349556
- PluginViewType as a2,
349557
- PluginCoordinationType as a3,
349558
- PluginJointFileType as a4,
349559
- PluginAsyncFunction as a5,
349560
- z as a6,
350361
+ diffCameraState as a0,
350362
+ Vitessce as a1,
350363
+ PluginFileType as a2,
350364
+ PluginViewType as a3,
350365
+ PluginCoordinationType as a4,
350366
+ PluginJointFileType as a5,
350367
+ PluginAsyncFunction as a6,
350368
+ z as a7,
349561
350369
  LercAddCompression as b,
349562
350370
  commonjsGlobal$1 as c,
349563
350371
  requireAssign as d,