@vitessce/launcher 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.
@@ -6375,12 +6375,12 @@ function baseIsEqual$3(value2, other, bitmask, customizer, stack2) {
6375
6375
  var now$4 = function() {
6376
6376
  return root$d.Date.now();
6377
6377
  };
6378
- var FUNC_ERROR_TEXT$2 = "Expected a function";
6378
+ var FUNC_ERROR_TEXT$3 = "Expected a function";
6379
6379
  var nativeMax$3 = Math.max, nativeMin$1 = Math.min;
6380
6380
  function debounce$5(func, wait2, options) {
6381
6381
  var lastArgs, lastThis, maxWait, result, timerId, lastCallTime, lastInvokeTime = 0, leading = false, maxing = false, trailing = true;
6382
6382
  if (typeof func != "function") {
6383
- throw new TypeError(FUNC_ERROR_TEXT$2);
6383
+ throw new TypeError(FUNC_ERROR_TEXT$3);
6384
6384
  }
6385
6385
  wait2 = toNumber$3(wait2) || 0;
6386
6386
  if (isObject$f(options)) {
@@ -6538,6 +6538,22 @@ var range$e = createRange();
6538
6538
  function sum$6(array2) {
6539
6539
  return array2 && array2.length ? baseSum(array2, identity$h) : 0;
6540
6540
  }
6541
+ var FUNC_ERROR_TEXT$2 = "Expected a function";
6542
+ function throttle$1(func, wait2, options) {
6543
+ var leading = true, trailing = true;
6544
+ if (typeof func != "function") {
6545
+ throw new TypeError(FUNC_ERROR_TEXT$2);
6546
+ }
6547
+ if (isObject$f(options)) {
6548
+ leading = "leading" in options ? !!options.leading : leading;
6549
+ trailing = "trailing" in options ? !!options.trailing : trailing;
6550
+ }
6551
+ return debounce$5(func, wait2, {
6552
+ "leading": leading,
6553
+ "maxWait": wait2,
6554
+ "trailing": trailing
6555
+ });
6556
+ }
6541
6557
  var INFINITY = 1 / 0;
6542
6558
  var createSet = !(Set$3 && 1 / setToArray$2(new Set$3([, -0]))[1] == INFINITY) ? noop$h : function(values3) {
6543
6559
  return new Set$3(values3);
@@ -10382,6 +10398,7 @@ const CoordinationType$1 = {
10382
10398
  SPATIAL_CHANNEL_LABELS_VISIBLE: "spatialChannelLabelsVisible",
10383
10399
  SPATIAL_CHANNEL_LABELS_ORIENTATION: "spatialChannelLabelsOrientation",
10384
10400
  SPATIAL_CHANNEL_LABEL_SIZE: "spatialChannelLabelSize",
10401
+ SPATIAL_CHANNELS_SORT_ORDER: "spatialChannelsSortOrder",
10385
10402
  // Multi-sample / comparative
10386
10403
  SAMPLE_TYPE: "sampleType",
10387
10404
  SAMPLE_SELECTION: "sampleSelection",
@@ -10434,7 +10451,9 @@ const ViewHelpMapping = {
10434
10451
  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).",
10435
10452
  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.",
10436
10453
  SAMPLE_SET_PAIR_MANAGER: "Select pairs of sample groups.",
10437
- FEATURE_STATS_TABLE: "This table displays per-feature statistics, for example, from a differential expression test."
10454
+ FEATURE_STATS_TABLE: "This table displays per-feature statistics, for example, from a differential expression test.",
10455
+ LAYER_CONTROLLER_BETA: "The spatial layer controller provides an interface for manipulating the visualization layers displayed in the spatial view.",
10456
+ 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)."
10438
10457
  };
10439
10458
  ({
10440
10459
  // For new file types
@@ -10985,6 +11004,7 @@ const COMPONENT_COORDINATION_TYPES = {
10985
11004
  CoordinationType$1.SPATIAL_CHANNEL_LABELS_VISIBLE,
10986
11005
  CoordinationType$1.SPATIAL_CHANNEL_LABELS_ORIENTATION,
10987
11006
  CoordinationType$1.SPATIAL_CHANNEL_LABEL_SIZE,
11007
+ CoordinationType$1.SPATIAL_CHANNELS_SORT_ORDER,
10988
11008
  CoordinationType$1.PHOTOMETRIC_INTERPRETATION
10989
11009
  ],
10990
11010
  [ViewType$1.HEATMAP]: [
@@ -11166,7 +11186,8 @@ const COMPONENT_COORDINATION_TYPES = {
11166
11186
  CoordinationType$1.LEGEND_VISIBLE,
11167
11187
  CoordinationType$1.SPATIAL_CHANNEL_LABELS_VISIBLE,
11168
11188
  CoordinationType$1.SPATIAL_CHANNEL_LABELS_ORIENTATION,
11169
- CoordinationType$1.SPATIAL_CHANNEL_LABEL_SIZE
11189
+ CoordinationType$1.SPATIAL_CHANNEL_LABEL_SIZE,
11190
+ CoordinationType$1.SPATIAL_CHANNELS_SORT_ORDER
11170
11191
  ],
11171
11192
  [ViewType$1.GENOMIC_PROFILES]: [
11172
11193
  CoordinationType$1.DATASET,
@@ -11877,7 +11898,18 @@ const ngPrecomputedMeshSchema = z.object({
11877
11898
  const ngPointAnnotationSchema = z.object({
11878
11899
  projectionAnnotationSpacing: z.number(),
11879
11900
  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."),
11880
- 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.")
11901
+ 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."),
11902
+ transform: z.object({
11903
+ matrix: z.array(z.array(z.number())),
11904
+ outputDimensions: z.record(z.tuple([z.number(), z.string()]))
11905
+ }).optional().describe("A coordinate transformation matrix to apply to the annotation layer source to map annotation coordinates to the global coordinate space."),
11906
+ // Note: See comments in neuroglancer-sorger.js
11907
+ // We may remove these in the future, pending clarification
11908
+ // to questions in the tissue-map-tools repo
11909
+ // - https://github.com/hms-dbmi/tissue-map-tools/issues/20
11910
+ // - https://github.com/hms-dbmi/tissue-map-tools/issues/38
11911
+ quantitativeColorProp: z.string().optional(),
11912
+ quantitativeColorMax: z.number().optional()
11881
11913
  }).partial().nullable();
11882
11914
  const obsEmbeddingAnndataSchema = annDataObsEmbedding;
11883
11915
  const obsSpotsAnndataSchema = annDataObsLocations;
@@ -19290,7 +19322,7 @@ const { tss } = createTss({
19290
19322
  },
19291
19323
  "usePlugin": useMuiThemeStyleOverridesPlugin
19292
19324
  });
19293
- const useStyles$y = tss.create({});
19325
+ const useStyles$z = tss.create({});
19294
19326
  function createChainedFunction$1(...funcs) {
19295
19327
  return funcs.reduce((acc, func) => {
19296
19328
  if (func == null) {
@@ -74100,7 +74132,7 @@ const Popper = React.forwardRef((props, ref2) => {
74100
74132
  }, [handlePopperRef]);
74101
74133
  React.useImperativeHandle(popperRefProp, () => popperRef.current, []);
74102
74134
  const [exited, setExited] = React.useState(true);
74103
- const { theme } = useStyles$y();
74135
+ const { theme } = useStyles$z();
74104
74136
  const rtlPlacement = flipPlacement(initialPlacement, theme);
74105
74137
  const [placement, setPlacement] = React.useState(rtlPlacement);
74106
74138
  React.useEffect(() => {
@@ -74834,7 +74866,7 @@ var VariableSizeList$1 = /* @__PURE__ */ createListComponent({
74834
74866
  });
74835
74867
  const VariableSizeList = VariableSizeList$1;
74836
74868
  const LIST_ROW_HEIGHT$1 = 48;
74837
- const useStyles$x = makeStyles()(() => ({
74869
+ const useStyles$y = makeStyles()(() => ({
74838
74870
  searchInput: {
74839
74871
  lineHeight: "initial",
74840
74872
  height: "auto !important"
@@ -74874,7 +74906,7 @@ const ListboxComponent$1 = React__default.forwardRef((props, ref2) => {
74874
74906
  });
74875
74907
  function SimpleAutocomplete(props) {
74876
74908
  const { getMatches, onChange, textInputLabel = "Enter a gene", getItemLabel = (option) => option.label } = props;
74877
- const { classes: classes2 } = useStyles$x();
74909
+ const { classes: classes2 } = useStyles$y();
74878
74910
  const [potentialItems, setPotentialItems] = useState([]);
74879
74911
  const handleInputChange = async (_event, value2) => {
74880
74912
  if (!value2) {
@@ -84424,7 +84456,7 @@ function defaultUrlTransform(value2) {
84424
84456
  }
84425
84457
  return "";
84426
84458
  }
84427
- const useStyles$w = makeStyles()((theme) => ({
84459
+ const useStyles$x = makeStyles()((theme) => ({
84428
84460
  description: {
84429
84461
  "& p, details, table": {
84430
84462
  fontSize: "80%",
@@ -84461,7 +84493,7 @@ const useStyles$w = makeStyles()((theme) => ({
84461
84493
  }));
84462
84494
  function Description(props) {
84463
84495
  const { description: description2, metadata: metadata2, descriptionType } = props;
84464
- const { classes: classes2 } = useStyles$w();
84496
+ const { classes: classes2 } = useStyles$x();
84465
84497
  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)] });
84466
84498
  }
84467
84499
  function DescriptionSubscriber(props) {
@@ -92713,6 +92745,37 @@ function dataToCellSetsTree(data2, options) {
92713
92745
  return cellSetsTree;
92714
92746
  }
92715
92747
  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]];
92748
+ 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]];
92749
+ 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]];
92750
+ const schemeJet = Array.from({ length: 256 }, (_, i2) => {
92751
+ const t4 = i2 / 255;
92752
+ let r3;
92753
+ let g2;
92754
+ let b2;
92755
+ if (t4 < 0.125) {
92756
+ r3 = 0;
92757
+ g2 = 0;
92758
+ b2 = 0.5 + t4 * 4;
92759
+ } else if (t4 < 0.375) {
92760
+ r3 = 0;
92761
+ g2 = (t4 - 0.125) * 4;
92762
+ b2 = 1;
92763
+ } else if (t4 < 0.625) {
92764
+ r3 = (t4 - 0.375) * 4;
92765
+ g2 = 1;
92766
+ b2 = 1 - (t4 - 0.375) * 4;
92767
+ } else if (t4 < 0.875) {
92768
+ r3 = 1;
92769
+ g2 = 1 - (t4 - 0.625) * 4;
92770
+ b2 = 0;
92771
+ } else {
92772
+ r3 = 1 - (t4 - 0.875) * 4;
92773
+ g2 = 0;
92774
+ b2 = 0;
92775
+ }
92776
+ return [Math.round(r3 * 255), Math.round(g2 * 255), Math.round(b2 * 255)];
92777
+ });
92778
+ const schemeGreys = Array.from({ length: 256 }, (_, i2) => [i2, i2, i2]);
92716
92779
  function rgbSpline$1(spline) {
92717
92780
  return (colors2) => {
92718
92781
  const n3 = colors2.length;
@@ -92750,7 +92813,15 @@ function basis$2(values3) {
92750
92813
  };
92751
92814
  }
92752
92815
  const interpolateRgbBasis = rgbSpline$1(basis$2);
92816
+ function interpolateSequentialMulti(range2) {
92817
+ const n3 = range2.length;
92818
+ return (t4) => range2[Math.max(0, Math.min(n3 - 1, Math.floor(t4 * n3)))];
92819
+ }
92753
92820
  interpolateRgbBasis(schemeRdBu);
92821
+ const interpolatePlasma = interpolateSequentialMulti(schemePlasma);
92822
+ const interpolateViridis = interpolateSequentialMulti(schemeViridis);
92823
+ const interpolateJet = interpolateSequentialMulti(schemeJet);
92824
+ const interpolateGreys = interpolateSequentialMulti(schemeGreys);
92754
92825
  function getCellColors(params2) {
92755
92826
  const { cellSets, cellSetSelection, cellSetColor, obsIndex, theme } = params2;
92756
92827
  if (cellSetSelection && cellSets) {
@@ -99532,7 +99603,7 @@ const Tooltip$2 = /* @__PURE__ */ React.forwardRef((props, ref2) => {
99532
99603
  }, extraProps), getChildren());
99533
99604
  });
99534
99605
  const nodeHeight = 32;
99535
- const useStyles$v = makeStyles()((theme) => ({
99606
+ const useStyles$w = makeStyles()((theme) => ({
99536
99607
  setsManager: {
99537
99608
  position: "relative",
99538
99609
  width: "100%",
@@ -99699,7 +99770,7 @@ const useStyles$v = makeStyles()((theme) => ({
99699
99770
  }));
99700
99771
  function SetsManagerTreeGlobalStyles(props) {
99701
99772
  const { classes: classes2 } = props;
99702
- const { theme } = useStyles$y();
99773
+ const { theme } = useStyles$z();
99703
99774
  return jsx$1(ScopedGlobalStyles, { parentClassName: classes2.setsManagerTree, styles: {
99704
99775
  ".rc-tree": {
99705
99776
  paddingLeft: "0",
@@ -100040,7 +100111,7 @@ function HelpTooltipGlobalStyles(props) {
100040
100111
  }
100041
100112
  function PopoverGlobalStyles(props) {
100042
100113
  const { classes: classes2 } = props;
100043
- const { theme } = useStyles$y();
100114
+ const { theme } = useStyles$z();
100044
100115
  return jsx$1(ScopedGlobalStyles, { parentClassName: classes2.popover, styles: {
100045
100116
  ".rc-tooltip-inner": {
100046
100117
  boxSizing: "border-box",
@@ -104724,7 +104795,7 @@ function NamedSetNodeStatic(props) {
104724
104795
  const onClick = level === 0 && !expanded ? () => onCheckLevel(nodeKey, nextLevelToCheck) : () => onNodeView(path2);
104725
104796
  const tooltipProps = disableTooltip ? { visible: false } : {};
104726
104797
  const popoverMenuConfig = makeNodeViewMenuConfig(props);
104727
- const { classes: classes2 } = useStyles$v();
104798
+ const { classes: classes2 } = useStyles$w();
104728
104799
  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 })] });
104729
104800
  }
104730
104801
  function NamedSetNodeEditing(props) {
@@ -104736,7 +104807,7 @@ function NamedSetNodeEditing(props) {
104736
104807
  onNodeSetName(path2, currentTitle, true);
104737
104808
  }
104738
104809
  }
104739
- const { classes: classes2 } = useStyles$v();
104810
+ const { classes: classes2 } = useStyles$w();
104740
104811
  return jsxs("span", { className: classes2.titleButtonWithInput, children: [jsx$1("input", {
104741
104812
  // eslint-disable-next-line jsx-a11y/no-autofocus
104742
104813
  autoFocus: true,
@@ -104762,7 +104833,7 @@ function LevelsButtons(props) {
104762
104833
  onCheckLevel(nodeKey, newLevel);
104763
104834
  }
104764
104835
  }
104765
- const { classes: classes2 } = useStyles$v();
104836
+ const { classes: classes2 } = useStyles$w();
104766
104837
  return jsx$1("div", { className: classes2.levelButtonsContainer, children: range$e(1, height2 + 1).map((i2) => {
104767
104838
  const isChecked = isEqual$3(path2, checkedLevelPath) && i2 === checkedLevelIndex;
104768
104839
  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);
@@ -104846,7 +104917,7 @@ let TreeNode$1 = class TreeNode extends TreeNode$2 {
104846
104917
  };
104847
104918
  function PlusButton(props) {
104848
104919
  const { datatype, onError, onImportTree, onCreateLevelZeroNode, importable, editable } = props;
104849
- const { classes: classes2 } = useStyles$v();
104920
+ const { classes: classes2 } = useStyles$w();
104850
104921
  const onImport = useCallback((importHandler, mimeType) => () => {
104851
104922
  const uploadInputNode = document.createElement("input");
104852
104923
  uploadInputNode.setAttribute("type", "file");
@@ -104963,7 +105034,7 @@ function SetsManager(props) {
104963
105034
  }, onNodeCheckNewName, onNodeSetIsEditing: setIsEditingNodeName, onNodeRemove, onExportLevelZeroNodeJSON, onExportLevelZeroNodeTabular, onExportSetJSON, disableTooltip: isDragging, onDragStart: () => setIsDragging(true), onDragEnd: () => setIsDragging(false), children: renderTreeNodes(node2.children, readOnly, newPath) }, pathToKey(newPath));
104964
105035
  });
104965
105036
  }
104966
- const { classes: classes2 } = useStyles$v();
105037
+ const { classes: classes2 } = useStyles$w();
104967
105038
  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) => {
104968
105039
  const { eventKey: dropKey } = info2.node.props;
104969
105040
  const { eventKey: dragKey } = info2.dragNode.props;
@@ -210928,22 +210999,22 @@ async function getDecoder(fileDirectory) {
210928
210999
  const Decoder = await importFn();
210929
211000
  return new Decoder(fileDirectory);
210930
211001
  }
210931
- addDecoder([void 0, 1], () => import("./raw-Dcz65jWu.js").then((m2) => m2.default));
210932
- addDecoder(5, () => import("./lzw-BlNLuocx.js").then((m2) => m2.default));
211002
+ addDecoder([void 0, 1], () => import("./raw-DMNeLVd7.js").then((m2) => m2.default));
211003
+ addDecoder(5, () => import("./lzw-BmHVONBc.js").then((m2) => m2.default));
210933
211004
  addDecoder(6, () => {
210934
211005
  throw new Error("old style JPEG compression is not supported.");
210935
211006
  });
210936
- addDecoder(7, () => import("./jpeg-4XvdCEqV.js").then((m2) => m2.default));
210937
- addDecoder([8, 32946], () => import("./deflate-nYfk3Mbe.js").then((m2) => m2.default));
210938
- addDecoder(32773, () => import("./packbits-D7jhgu7h.js").then((m2) => m2.default));
211007
+ addDecoder(7, () => import("./jpeg-BGB-_EKG.js").then((m2) => m2.default));
211008
+ addDecoder([8, 32946], () => import("./deflate-38TS7div.js").then((m2) => m2.default));
211009
+ addDecoder(32773, () => import("./packbits-DKF2IGBX.js").then((m2) => m2.default));
210939
211010
  addDecoder(
210940
211011
  34887,
210941
- () => import("./lerc-J5N91MJw.js").then(async (m2) => {
211012
+ () => import("./lerc-Db0ZEZ3c.js").then(async (m2) => {
210942
211013
  await m2.zstd.init();
210943
211014
  return m2;
210944
211015
  }).then((m2) => m2.default)
210945
211016
  );
210946
- addDecoder(50001, () => import("./webimage-CMXekglL.js").then((m2) => m2.default));
211017
+ addDecoder(50001, () => import("./webimage-BTp31_-j.js").then((m2) => m2.default));
210947
211018
  function copyNewSize(array2, width2, height2, samplesPerPixel = 1) {
210948
211019
  return new (Object.getPrototypeOf(array2)).constructor(width2 * height2 * samplesPerPixel);
210949
211020
  }
@@ -227717,7 +227788,7 @@ class ContourLayerWithText extends ContourLayer {
227717
227788
  }
227718
227789
  ContourLayerWithText.layerName = "ContourLayerWithText";
227719
227790
  ContourLayerWithText.defaultProps = defaultProps;
227720
- const useStyles$u = makeStyles()(() => ({
227791
+ const useStyles$v = makeStyles()(() => ({
227721
227792
  toolButton: {
227722
227793
  display: "inline-flex",
227723
227794
  "&:active": {
@@ -227779,12 +227850,12 @@ const useStyles$u = makeStyles()(() => ({
227779
227850
  }));
227780
227851
  function IconTool(props) {
227781
227852
  const { alt, onClick, isActive: isActive2, children: children3 } = props;
227782
- const { classes: classes2 } = useStyles$u();
227853
+ const { classes: classes2 } = useStyles$v();
227783
227854
  return jsx$1("button", { className: clsx(classes2.toolIcon, { [classes2.toolActive]: isActive2 }), onClick, type: "button", title: alt, children: children3 });
227784
227855
  }
227785
227856
  function IconButton(props) {
227786
227857
  const { alt, onClick, children: children3 } = props;
227787
- const { classes: classes2 } = useStyles$u();
227858
+ const { classes: classes2 } = useStyles$v();
227788
227859
  return jsx$1("button", { className: clsx(classes2.toolIcon, classes2.toolButton), onClick, type: "button", title: alt, children: children3 });
227789
227860
  }
227790
227861
  function ToolMenu(props) {
@@ -227792,14 +227863,14 @@ function ToolMenu(props) {
227792
227863
  const lassoIconAltText = "Select lasso";
227793
227864
  const { setActiveTool, activeTool, visibleTools = { pan: true, selectLasso: true, recenter: true }, recenterOnClick = () => {
227794
227865
  } } = props;
227795
- const { classes: classes2 } = useStyles$u();
227866
+ const { classes: classes2 } = useStyles$v();
227796
227867
  const onRecenterButtonCLick = () => {
227797
227868
  recenterOnClick();
227798
227869
  };
227799
227870
  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] });
227800
227871
  }
227801
227872
  const getCursorWithTool = () => "crosshair";
227802
- const getCursor = (interactionState) => interactionState.isDragging ? "grabbing" : "default";
227873
+ const getCursor = (interactionState) => interactionState.isDragging ? "grabbing" : "grab";
227803
227874
  function getOnHoverCallback(obsIndex, setObsHighlight, setComponentHover) {
227804
227875
  return (info2) => {
227805
227876
  if (setComponentHover) {
@@ -228949,7 +229020,7 @@ function ScatterplotOptions(props) {
228949
229020
  id: `feature-aggregation-strategy-${scatterplotOptionsId}`
228950
229021
  }, children: FEATURE_AGGREGATION_STRATEGIES$2.map((opt) => jsx$1("option", { value: opt, children: capitalize$3(opt) }, opt)) }) })] }) : null] });
228951
229022
  }
228952
- const useStyles$t = makeStyles()((theme) => ({
229023
+ const useStyles$u = makeStyles()((theme) => ({
228953
229024
  tooltipAnchor: {
228954
229025
  position: "relative",
228955
229026
  width: "0px",
@@ -228980,7 +229051,7 @@ const useStyles$t = makeStyles()((theme) => ({
228980
229051
  function Tooltip3(props) {
228981
229052
  const { x: x2, y: y2, parentWidth, parentHeight, children: children3 } = props;
228982
229053
  const ref2 = useRef();
228983
- const { classes: classes2 } = useStyles$t();
229054
+ const { classes: classes2 } = useStyles$u();
228984
229055
  const [placementX, setPlacementX] = useState("start");
228985
229056
  const [placementY, setPlacementY] = useState("bottom");
228986
229057
  const getTooltipContainer = useVitessceContainer(ref2);
@@ -228996,7 +229067,7 @@ function Tooltip3(props) {
228996
229067
  }, [x2, y2, parentWidth, parentHeight]);
228997
229068
  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 });
228998
229069
  }
228999
- const useStyles$s = makeStyles()((theme) => ({
229070
+ const useStyles$t = makeStyles()((theme) => ({
229000
229071
  cellEmphasisCrosshair: {
229001
229072
  zIndex: 50,
229002
229073
  position: "absolute",
@@ -229007,7 +229078,7 @@ const useStyles$s = makeStyles()((theme) => ({
229007
229078
  }));
229008
229079
  function Tooltip2D(props) {
229009
229080
  const { parentUuid, sourceUuid, x: x2, y: y2, parentWidth, parentHeight, children: children3 } = props;
229010
- const { classes: classes2 } = useStyles$s();
229081
+ const { classes: classes2 } = useStyles$t();
229011
229082
  if (x2 < 0 || x2 > parentWidth || y2 < 0 || y2 > parentHeight) {
229012
229083
  return null;
229013
229084
  }
@@ -232919,7 +232990,7 @@ function getXlinkHref(cmap) {
232919
232990
  const xlinkHref = ramp(color2.copy().domain(quantize$3(interpolate$1$1(0, 1), n3))).toDataURL();
232920
232991
  return xlinkHref;
232921
232992
  }
232922
- const useStyles$r = makeStyles()(() => ({
232993
+ const useStyles$s = makeStyles()(() => ({
232923
232994
  legend: {
232924
232995
  position: "relative",
232925
232996
  // Needed for absolute positioning of slider overlay
@@ -233116,7 +233187,7 @@ function Legend(props) {
233116
233187
  contourThresholds
233117
233188
  } = props;
233118
233189
  const svgRef = useRef(null);
233119
- const { classes: classes2 } = useStyles$r();
233190
+ const { classes: classes2 } = useStyles$s();
233120
233191
  const [localRange, setLocalRange] = useState(featureValueColormapRange);
233121
233192
  useEffect(() => {
233122
233193
  setLocalRange(featureValueColormapRange);
@@ -233450,7 +233521,7 @@ function Legend(props) {
233450
233521
  }
233451
233522
  } })] })] });
233452
233523
  }
233453
- const useStyles$q = makeStyles()(() => ({
233524
+ const useStyles$r = makeStyles()(() => ({
233454
233525
  multiLegend: {
233455
233526
  position: "absolute",
233456
233527
  top: "0px",
@@ -233477,7 +233548,7 @@ function MultiLegend(props) {
233477
233548
  pointLayerCoordination,
233478
233549
  pointMultiIndicesData
233479
233550
  } = props;
233480
- const { classes: classes2 } = useStyles$q();
233551
+ const { classes: classes2 } = useStyles$r();
233481
233552
  const reversedSegmentationLayerScopes = useMemo$1(() => [...segmentationLayerScopes || []].reverse(), [segmentationLayerScopes]);
233482
233553
  const reversedSpotLayerScopes = useMemo$1(() => [...spotLayerScopes || []].reverse(), [spotLayerScopes]);
233483
233554
  const reversedPointLayerScopes = useMemo$1(() => [...pointLayerScopes || []].reverse(), [pointLayerScopes]);
@@ -233570,7 +233641,7 @@ function MultiLegend(props) {
233570
233641
  }) : null;
233571
233642
  }) : null] });
233572
233643
  }
233573
- const useStyles$p = makeStyles()(() => ({
233644
+ const useStyles$q = makeStyles()(() => ({
233574
233645
  channelNamesLegendContainer: {
233575
233646
  position: "absolute",
233576
233647
  bottom: "0px",
@@ -233593,7 +233664,7 @@ const useStyles$p = makeStyles()(() => ({
233593
233664
  }));
233594
233665
  function ChannelNamesLegend(props) {
233595
233666
  const { images, imageLayerScopes, imageLayerCoordination, imageChannelScopesByLayer, imageChannelCoordination } = props;
233596
- const { classes: classes2 } = useStyles$p();
233667
+ const { classes: classes2 } = useStyles$q();
233597
233668
  const reversedImageLayerScopes = useMemo$1(() => [...imageLayerScopes || []].reverse(), [imageLayerScopes]);
233598
233669
  return jsx$1("div", { className: classes2.channelNamesLegendContainer, children: imageLayerScopes ? reversedImageLayerScopes.map((layerScope) => {
233599
233670
  const layerCoordination = imageLayerCoordination[0][layerScope];
@@ -255200,7 +255271,7 @@ class Spatial2 extends AbstractSpatialOrScatterplot {
255200
255271
  this.viewInfoDidUpdate();
255201
255272
  const shallowDiff2 = (propName) => prevProps[propName] !== this.props[propName];
255202
255273
  const shallowDiffByLayer2 = (propName, scopeName) => prevProps?.[propName]?.[scopeName] !== this.props?.[propName]?.[scopeName];
255203
- const shallowDiffByChannel = (propName, firstName, secondName) => prevProps?.[propName]?.[firstName]?.[secondName] !== this.props?.[propName]?.[firstName]?.[secondName];
255274
+ const shallowDiffByChannel2 = (propName, firstName, secondName) => prevProps?.[propName]?.[firstName]?.[secondName] !== this.props?.[propName]?.[firstName]?.[secondName];
255204
255275
  const shallowDiffByLayerCoordination = (propName, layerScope) => prevProps?.[propName]?.[0]?.[layerScope] !== this.props?.[propName]?.[0]?.[layerScope];
255205
255276
  const shallowDiffByChannelCoordination = (propName, layerScope, channelScope) => prevProps?.[propName]?.[0]?.[layerScope]?.[channelScope] !== this.props?.[propName]?.[0]?.[layerScope]?.[channelScope];
255206
255277
  let forceUpdate = false;
@@ -255221,7 +255292,7 @@ class Spatial2 extends AbstractSpatialOrScatterplot {
255221
255292
  } else {
255222
255293
  this.props.segmentationLayerScopes?.forEach((layerScope) => {
255223
255294
  this.props.segmentationChannelScopesByLayer?.[layerScope]?.forEach((channelScope) => {
255224
- if (shallowDiffByChannel("obsSegmentationsLocations", layerScope, channelScope)) {
255295
+ if (shallowDiffByChannel2("obsSegmentationsLocations", layerScope, channelScope)) {
255225
255296
  this.onUpdateSegmentationsLocationsData(layerScope, channelScope);
255226
255297
  forceUpdate = true;
255227
255298
  }
@@ -255234,7 +255305,7 @@ class Spatial2 extends AbstractSpatialOrScatterplot {
255234
255305
  } else {
255235
255306
  this.props.segmentationLayerScopes?.forEach((layerScope) => {
255236
255307
  this.props.segmentationChannelScopesByLayer?.[layerScope]?.forEach((channelScope) => {
255237
- if (shallowDiffByChannel("obsSegmentationsSets", layerScope, channelScope) || shallowDiffByChannelCoordination("segmentationChannelCoordination", layerScope, channelScope)) {
255308
+ if (shallowDiffByChannel2("obsSegmentationsSets", layerScope, channelScope) || shallowDiffByChannelCoordination("segmentationChannelCoordination", layerScope, channelScope)) {
255238
255309
  this.onUpdateSegmentationsSetsData(layerScope, channelScope);
255239
255310
  forceUpdate = true;
255240
255311
  }
@@ -255247,7 +255318,7 @@ class Spatial2 extends AbstractSpatialOrScatterplot {
255247
255318
  } else {
255248
255319
  this.props.segmentationLayerScopes?.forEach((layerScope) => {
255249
255320
  this.props.segmentationChannelScopesByLayer?.[layerScope]?.forEach((channelScope) => {
255250
- if (shallowDiffByChannel("segmentationMatrixIndices", layerScope, channelScope)) {
255321
+ if (shallowDiffByChannel2("segmentationMatrixIndices", layerScope, channelScope)) {
255251
255322
  this.onUpdateSegmentationsIndexData(layerScope, channelScope);
255252
255323
  forceUpdate = true;
255253
255324
  }
@@ -255260,7 +255331,7 @@ class Spatial2 extends AbstractSpatialOrScatterplot {
255260
255331
  } else {
255261
255332
  this.props.segmentationLayerScopes.forEach((layerScope) => {
255262
255333
  this.props.segmentationChannelScopesByLayer?.[layerScope]?.forEach((channelScope) => {
255263
- if (shallowDiffByChannel("segmentationMatrixIndices", layerScope, channelScope) || shallowDiffByChannel("segmentationMultiExpressionData", layerScope, channelScope)) {
255334
+ if (shallowDiffByChannel2("segmentationMatrixIndices", layerScope, channelScope) || shallowDiffByChannel2("segmentationMultiExpressionData", layerScope, channelScope)) {
255264
255335
  this.onUpdateSegmentationsExpressionData(layerScope, channelScope);
255265
255336
  forceUpdate = true;
255266
255337
  }
@@ -255514,12 +255585,12 @@ class ErrorBoundary extends React__default.Component {
255514
255585
  }
255515
255586
  }
255516
255587
  const LazySpatialThree = React__default.lazy(async () => {
255517
- const { SpatialWrapper: SpatialWrapper2 } = await import("./index-C2U2CO2P.js").then((n3) => n3.a);
255588
+ const { SpatialWrapper: SpatialWrapper2 } = await import("./index-CA6Y_Dls.js").then((n3) => n3.a);
255518
255589
  return { default: SpatialWrapper2 };
255519
255590
  });
255520
255591
  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 }) }) }) }));
255521
255592
  const LazySpatialAccelerated = React__default.lazy(async () => {
255522
- const { SpatialWrapper: SpatialWrapper2 } = await import("./index-DuFOjj6d.js");
255593
+ const { SpatialWrapper: SpatialWrapper2 } = await import("./index-gStG4FEQ.js");
255523
255594
  return { default: SpatialWrapper2 };
255524
255595
  });
255525
255596
  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 }) }) }) }));
@@ -255696,7 +255767,8 @@ function SpatialSubscriber(props) {
255696
255767
  bitmaskValueIsIndex = false,
255697
255768
  // TODO: move to coordination type
255698
255769
  three: threeFor3d = false,
255699
- accelerated: acceleratedFor3d = false
255770
+ accelerated: acceleratedFor3d = false,
255771
+ helpText = ViewHelpMapping.SPATIAL_BETA
255700
255772
  } = props;
255701
255773
  const loaders = useLoaders();
255702
255774
  const setComponentHover = useSetComponentHover();
@@ -256140,7 +256212,7 @@ function SpatialSubscriber(props) {
256140
256212
  }
256141
256213
  }
256142
256214
  };
256143
- 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 ? {
256215
+ 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 ? {
256144
256216
  zoom: zoom2,
256145
256217
  target: [targetX2, targetY2, targetZ],
256146
256218
  rotationX,
@@ -263591,7 +263663,7 @@ _defineProperty$3(WindowScroller, "defaultProps", {
263591
263663
  serverHeight: 0,
263592
263664
  serverWidth: 0
263593
263665
  });
263594
- const useStyles$o = makeStyles()((theme) => ({
263666
+ const useStyles$p = makeStyles()((theme) => ({
263595
263667
  selectableTable: {
263596
263668
  flex: "1 1 auto",
263597
263669
  outline: "none"
@@ -263740,7 +263812,7 @@ function SelectableTable(props) {
263740
263812
  onChange(null);
263741
263813
  }
263742
263814
  }, [selectedRows, allowMultiple]);
263743
- const { classes: classes2 } = useStyles$o();
263815
+ const { classes: classes2 } = useStyles$p();
263744
263816
  const inputUuid = v4$1();
263745
263817
  const rowRenderer = ({ index: index2, style: style2 }) => (
263746
263818
  // eslint-disable-next-line jsx-a11y/interactive-supports-focus
@@ -263765,7 +263837,7 @@ const FEATURELIST_SORT_OPTIONS = [
263765
263837
  "original"
263766
263838
  ];
263767
263839
  const ALT_COLNAME = "Alternate ID";
263768
- const useStyles$n = makeStyles()(() => ({
263840
+ const useStyles$o = makeStyles()(() => ({
263769
263841
  searchBar: {
263770
263842
  marginTop: "10px",
263771
263843
  marginLeft: "10px",
@@ -263778,7 +263850,7 @@ const useStyles$n = makeStyles()(() => ({
263778
263850
  }));
263779
263851
  function FeatureList(props) {
263780
263852
  const { width: width2, height: height2, hasColorEncoding, geneList = [], featureLabelsMap, geneSelection = [], geneFilter = null, setGeneSelection, enableMultiSelect, showFeatureTable, featureListSort, featureListSortKey, hasFeatureLabels, primaryColumnName } = props;
263781
- const { classes: classes2 } = useStyles$n();
263853
+ const { classes: classes2 } = useStyles$o();
263782
263854
  const [searchTerm, setSearchTerm] = useState("");
263783
263855
  const selectableTableSortKey = featureListSortKey === "featureIndex" ? "key" : "name";
263784
263856
  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]);
@@ -263843,7 +263915,7 @@ function FeatureListOptions(props) {
263843
263915
  const { classes: classes2 } = usePlotOptionsStyles();
263844
263916
  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: {
263845
263917
  id: `feature-list-sort-option-${featureListId}`
263846
- }, 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: {
263918
+ }, 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: {
263847
263919
  "aria-label": "Select the feature list sort key",
263848
263920
  id: `feature-list-sort-key-${featureListId}`
263849
263921
  }, 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: {
@@ -263851,7 +263923,7 @@ function FeatureListOptions(props) {
263851
263923
  id: `feature-list-show-alternative-ids-${featureListId}`
263852
263924
  } } }) })] })] }) : null] });
263853
263925
  }
263854
- const useStyles$m = makeStyles()((theme) => ({
263926
+ const useStyles$n = makeStyles()((theme) => ({
263855
263927
  featureListContainer: {
263856
263928
  width: "100%",
263857
263929
  height: "100%",
@@ -263866,7 +263938,7 @@ function FeatureListSubscriber(props) {
263866
263938
  const loaders = useLoaders();
263867
263939
  const coordinationScopes = useCoordinationScopes(coordinationScopesRaw);
263868
263940
  const [width2, height2, containerRef] = useGridItemSize();
263869
- const { classes: classes2 } = useStyles$m();
263941
+ const { classes: classes2 } = useStyles$n();
263870
263942
  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);
263871
263943
  const variablesLabel = variablesLabelOverride || featureType;
263872
263944
  const title2 = titleOverride || `${capitalize$3(variablesLabel)} List`;
@@ -264001,7 +264073,7 @@ const useSelectionSliderStyles = makeStyles()(() => ({
264001
264073
  backgroundColor: "rgba(128, 128, 128, 0.7)"
264002
264074
  }
264003
264075
  }));
264004
- const useStyles$l = makeStyles()((theme) => ({
264076
+ const useStyles$m = makeStyles()((theme) => ({
264005
264077
  paletteContainer: {
264006
264078
  width: "70px",
264007
264079
  height: "40px",
@@ -264022,10 +264094,10 @@ const useStyles$l = makeStyles()((theme) => ({
264022
264094
  }
264023
264095
  }));
264024
264096
  const ColorPalette = ({ handleChange }) => {
264025
- const { classes: classes2 } = useStyles$l();
264097
+ const { classes: classes2 } = useStyles$m();
264026
264098
  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)) });
264027
264099
  };
264028
- const useStyles$k = makeStyles()(() => ({
264100
+ const useStyles$l = makeStyles()(() => ({
264029
264101
  menuButton: {
264030
264102
  backgroundColor: "transparent"
264031
264103
  },
@@ -264044,7 +264116,7 @@ function MuiSpan(props) {
264044
264116
  }
264045
264117
  function ChannelOptions$1({ handlePropertyChange, handleChannelRemove, handleIQRUpdate }) {
264046
264118
  const [open2, setOpen] = useState(false);
264047
- const { classes: classes2 } = useStyles$k();
264119
+ const { classes: classes2 } = useStyles$l();
264048
264120
  const handleColorSelect = (color2) => {
264049
264121
  handlePropertyChange("color", color2);
264050
264122
  };
@@ -264661,7 +264733,7 @@ function LayerController$1(props) {
264661
264733
  minWidth: "50%"
264662
264734
  } })] }), 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] })] });
264663
264735
  }
264664
- const useStyles$j = makeStyles()(() => ({
264736
+ const useStyles$k = makeStyles()(() => ({
264665
264737
  addButton: {
264666
264738
  marginTop: "10px",
264667
264739
  marginBottom: "10px",
@@ -264673,7 +264745,7 @@ function ImageAddIcon() {
264673
264745
  }
264674
264746
  function ImageAddButton({ imageOptions, handleImageAdd }) {
264675
264747
  const [open2, setOpen] = useState(false);
264676
- const { classes: classes2 } = useStyles$j();
264748
+ const { classes: classes2 } = useStyles$k();
264677
264749
  const handleAdd = (imgData) => {
264678
264750
  setOpen((prev2) => !prev2);
264679
264751
  handleImageAdd(imgData);
@@ -265005,7 +265077,7 @@ const useEllipsisMenuStyles = makeStyles()(() => ({
265005
265077
  padding: "4px"
265006
265078
  }
265007
265079
  }));
265008
- const useStyles$i = makeStyles()(() => ({
265080
+ const useStyles$j = makeStyles()(() => ({
265009
265081
  colorPickerPaper: {
265010
265082
  overflow: "hidden",
265011
265083
  "& > ul": {
@@ -265063,7 +265135,7 @@ function ChannelColorPickerMenu(props) {
265063
265135
  setColor2([rgb2.r, rgb2.g, rgb2.b]);
265064
265136
  }
265065
265137
  }
265066
- const { classes: classes2 } = useStyles$i();
265138
+ const { classes: classes2 } = useStyles$j();
265067
265139
  const currentColor = color2 ? colorArrayToString(color2) : colorArrayToString(getDefaultColor(theme));
265068
265140
  const isWritable = typeof setColor2 === "function";
265069
265141
  const derivedOpen = isWritable ? isStaticColor && visible ? open2 : false : false;
@@ -265071,7 +265143,7 @@ function ChannelColorPickerMenu(props) {
265071
265143
  [classes2.colorIconOutline]: theme !== "dark" && isEqual$3(color2, [255, 255, 255])
265072
265144
  }), 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 }) });
265073
265145
  }
265074
- const useStyles$h = makeStyles()(() => ({
265146
+ const useStyles$i = makeStyles()(() => ({
265075
265147
  layerTypeSpotIcon: {
265076
265148
  height: "100%",
265077
265149
  marginLeft: "1px",
@@ -265084,7 +265156,7 @@ const useStyles$h = makeStyles()(() => ({
265084
265156
  function SpotLayerEllipsisMenu(props) {
265085
265157
  const { strokeWidth, setStrokeWidth, filled, setFilled, featureSelection, obsColorEncoding, setObsColorEncoding, featureValueColormap, setFeatureValueColormap, featureValueColormapRange, setFeatureValueColormapRange, tooltipsVisible, setTooltipsVisible, tooltipCrosshairsVisible, setTooltipCrosshairsVisible, legendVisible, setLegendVisible } = props;
265086
265158
  const [open2, setOpen] = useState(false);
265087
- const { classes: classes2 } = useStyles$h();
265159
+ const { classes: classes2 } = useStyles$i();
265088
265160
  const { classes: selectClasses2 } = useSelectStyles();
265089
265161
  const { classes: menuClasses } = useEllipsisMenuStyles();
265090
265162
  const filledId = $bdb11010cef70236$export$f680877a34711e37();
@@ -265106,7 +265178,7 @@ function SpotLayerController(props) {
265106
265178
  const Visibility = useMemo$1(() => visibleSetting ? VisibilityIcon : VisibilityOffIcon, [visibleSetting]);
265107
265179
  const isStaticColor = obsColorEncoding === "spatialLayerColor";
265108
265180
  const isColormap = obsColorEncoding === "geneSelection";
265109
- const { classes: classes2 } = useStyles$h();
265181
+ const { classes: classes2 } = useStyles$i();
265110
265182
  const { classes: lcClasses } = useControllerSectionStyles();
265111
265183
  const { classes: menuClasses } = useEllipsisMenuStyles();
265112
265184
  const handleVisibleChange = useCallback(() => {
@@ -265116,7 +265188,7 @@ function SpotLayerController(props) {
265116
265188
  const handleOpacityChange = useCallback((e3, v) => setOpacity2(v), [setOpacity2]);
265117
265189
  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 }) })] }) }) });
265118
265190
  }
265119
- const useStyles$g = makeStyles()(() => ({
265191
+ const useStyles$h = makeStyles()(() => ({
265120
265192
  layerFeatureControllerGrid: {
265121
265193
  padding: "0",
265122
265194
  flexWrap: "nowrap"
@@ -265161,14 +265233,15 @@ function LayerPerFeatureController(props) {
265161
265233
  ]);
265162
265234
  }
265163
265235
  }, [featureName, featureColor, setFeatureColor, featureColorIndex]);
265164
- const { classes: classes2 } = useStyles$g();
265236
+ const { classes: classes2 } = useStyles$h();
265165
265237
  const { classes: lcClasses } = useControllerSectionStyles();
265166
265238
  const { classes: menuClasses } = useEllipsisMenuStyles();
265167
265239
  const [open2, setOpen] = useState(false);
265168
265240
  const { colorPickerColor, colorPickerReadable, colorPickerWritable, colorPickerTooltip } = useMemo$1(() => {
265169
265241
  if (obsColorEncoding === "geneSelection") {
265242
+ const storedColor = featureColor?.find((fc) => fc.name === featureName)?.color;
265170
265243
  return {
265171
- colorPickerColor: featureColor?.[featureColorIndex]?.color ?? spatialLayerColor,
265244
+ colorPickerColor: storedColor ?? randomByFeatureColor ?? spatialLayerColor,
265172
265245
  colorPickerReadable: true,
265173
265246
  colorPickerWritable: true,
265174
265247
  colorPickerTooltip: null
@@ -265214,7 +265287,7 @@ function LayerPerFeatureController(props) {
265214
265287
  ]);
265215
265288
  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);
265216
265289
  }
265217
- const useStyles$f = makeStyles()(() => ({
265290
+ const useStyles$g = makeStyles()(() => ({
265218
265291
  pointLayerButton: {
265219
265292
  borderStyle: "dashed",
265220
265293
  marginTop: "10px",
@@ -265243,7 +265316,7 @@ const useStyles$f = makeStyles()(() => ({
265243
265316
  }
265244
265317
  }));
265245
265318
  function PointLayerEllipsisMenu(props) {
265246
- const { featureSelection, featureFilterMode, setFeatureFilterMode, obsColorEncoding, setObsColorEncoding, featureValueColormapRange, setFeatureValueColormapRange, tooltipsVisible, setTooltipsVisible, tooltipCrosshairsVisible, setTooltipCrosshairsVisible, legendVisible, setLegendVisible } = props;
265319
+ const { featureSelection, featureFilterMode, setFeatureFilterMode, obsColorEncoding, setObsColorEncoding, featureValueColormap, setFeatureValueColormap, featureValueColormapRange, setFeatureValueColormapRange, tooltipsVisible, setTooltipsVisible, tooltipCrosshairsVisible, setTooltipCrosshairsVisible, legendVisible, setLegendVisible } = props;
265247
265320
  const [open2, setOpen] = useState(false);
265248
265321
  const { classes: selectClasses2 } = useSelectStyles();
265249
265322
  const { classes: menuClasses } = useEllipsisMenuStyles();
@@ -265253,7 +265326,8 @@ function PointLayerEllipsisMenu(props) {
265253
265326
  const crosshairsVisibleId = $bdb11010cef70236$export$f680877a34711e37();
265254
265327
  const legendVisibleId = $bdb11010cef70236$export$f680877a34711e37();
265255
265328
  const featureFilterModeId = $bdb11010cef70236$export$f680877a34711e37();
265256
- 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" } } })] })] });
265329
+ const colormapSelectorId = $bdb11010cef70236$export$f680877a34711e37();
265330
+ 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" } } })] })] });
265257
265331
  }
265258
265332
  function PointLayerController(props) {
265259
265333
  const { theme, layerScope, layerCoordination, setLayerCoordination, palette = null, pointMatrixIndicesData, tiledPointsLoadingProgress, layerPerFeatureForPoints } = props;
@@ -265283,7 +265357,7 @@ function PointLayerController(props) {
265283
265357
  }, [featureColor, featureSelection]);
265284
265358
  const isStaticColor = obsColorEncoding === "spatialLayerColor" || obsColorEncoding === "geneSelection";
265285
265359
  const showStaticColor = obsColorEncoding === "spatialLayerColor" || obsColorEncoding === "geneSelection" && hasUnspecifiedFeatureColors;
265286
- const isColormap = false;
265360
+ const isColormap = obsColorEncoding === "geneSelection";
265287
265361
  const hasSingleSelectedFeature = obsColorEncoding === "geneSelection" && Array.isArray(featureSelection) && featureSelection.length === 1;
265288
265362
  const color2 = useMemo$1(() => {
265289
265363
  if (showStaticColor) {
@@ -265330,7 +265404,7 @@ function PointLayerController(props) {
265330
265404
  featureSelection,
265331
265405
  showStaticColor
265332
265406
  ]);
265333
- const { classes: classes2 } = useStyles$f();
265407
+ const { classes: classes2 } = useStyles$g();
265334
265408
  const { classes: lcClasses } = useControllerSectionStyles();
265335
265409
  const { classes: menuClasses } = useEllipsisMenuStyles();
265336
265410
  const handleVisibleChange = useCallback(() => {
@@ -265342,9 +265416,24 @@ function PointLayerController(props) {
265342
265416
  const [coloringTabIndex, setColoringTabIndex] = useState(0);
265343
265417
  const { featureIndex } = pointMatrixIndicesData || {};
265344
265418
  const hasSelectedFeatures = Array.isArray(featureSelection) && featureSelection.length > 0;
265345
- 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] });
265419
+ useEffect(() => {
265420
+ if (obsColorEncoding !== "geneSelection" || !Array.isArray(featureSelection))
265421
+ return;
265422
+ const cleanedFeatureColor = (featureColor ?? []).filter((fc) => featureSelection.includes(fc.name));
265423
+ const missingEntries = featureSelection.filter((featureName) => !cleanedFeatureColor.find((fc) => fc.name === featureName)).map((featureName) => {
265424
+ const varIdx = featureIndex?.indexOf(featureName) ?? -1;
265425
+ const fColor = varIdx >= 0 ? PALETTE[varIdx % PALETTE.length] : getDefaultColor(theme);
265426
+ return { name: featureName, color: fColor };
265427
+ });
265428
+ const nextFeatureColor = [...cleanedFeatureColor, ...missingEntries];
265429
+ const changed = nextFeatureColor.length !== featureColor?.length || missingEntries.length > 0;
265430
+ if (changed) {
265431
+ setFeatureColor(nextFeatureColor);
265432
+ }
265433
+ }, [featureSelection, obsColorEncoding, featureIndex, featureColor]);
265434
+ 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] });
265346
265435
  }
265347
- const useStyles$e = makeStyles()(() => ({
265436
+ const useStyles$f = makeStyles()(() => ({
265348
265437
  layerTypeSegmentationIcon: {
265349
265438
  height: "100%",
265350
265439
  marginLeft: "1px",
@@ -265357,7 +265446,7 @@ const useStyles$e = makeStyles()(() => ({
265357
265446
  function SegmentationChannelEllipsisMenu(props) {
265358
265447
  const { obsType, featureType, featureValueType, strokeWidth, setStrokeWidth, filled, setFilled, featureSelection, obsColorEncoding, setObsColorEncoding, featureValueColormap, setFeatureValueColormap, featureValueColormapRange, setFeatureValueColormapRange, tooltipsVisible, setTooltipsVisible, tooltipCrosshairsVisible, setTooltipCrosshairsVisible, legendVisible, setLegendVisible } = props;
265359
265448
  const [open2, setOpen] = useState(false);
265360
- const { classes: classes2 } = useStyles$e();
265449
+ const { classes: classes2 } = useStyles$f();
265361
265450
  const { classes: selectClasses2 } = useSelectStyles();
265362
265451
  const { classes: menuClasses } = useEllipsisMenuStyles();
265363
265452
  const filledId = $bdb11010cef70236$export$f680877a34711e37();
@@ -265376,7 +265465,7 @@ function SegmentationChannelController(props) {
265376
265465
  const Visibility = useMemo$1(() => visibleSetting ? VisibilityIcon : VisibilityOffIcon, [visibleSetting]);
265377
265466
  const isStaticColor = obsColorEncoding === "spatialChannelColor";
265378
265467
  const isColormap = obsColorEncoding === "geneSelection";
265379
- const { classes: classes2 } = useStyles$e();
265468
+ const { classes: classes2 } = useStyles$f();
265380
265469
  const { classes: lcClasses } = useControllerSectionStyles();
265381
265470
  const { classes: menuClasses } = useEllipsisMenuStyles();
265382
265471
  const handleVisibleChange = useCallback(() => {
@@ -268094,7 +268183,7 @@ function useBaseQuery(options, Observer, queryClient) {
268094
268183
  function useQuery(options, queryClient) {
268095
268184
  return useBaseQuery(options, QueryObserver);
268096
268185
  }
268097
- const useStyles$d = makeStyles()(() => ({
268186
+ const useStyles$e = makeStyles()(() => ({
268098
268187
  channelMenuButton: {
268099
268188
  backgroundColor: "transparent",
268100
268189
  padding: "3px 0",
@@ -268108,7 +268197,7 @@ const useStyles$d = makeStyles()(() => ({
268108
268197
  function ChannelOptions(props) {
268109
268198
  const { onRemove: onRemove2, onResetWindowUsingIQR, showValueExtent, setShowValueExtent, numResolutions, spatialMaxResolution, setSpatialMaxResolution } = props;
268110
268199
  const [open2, setOpen] = useState(false);
268111
- const { classes: classes2 } = useStyles$d();
268200
+ const { classes: classes2 } = useStyles$e();
268112
268201
  const { classes: selectClasses2 } = useSelectStyles();
268113
268202
  const { classes: menuClasses } = useEllipsisMenuStyles();
268114
268203
  function handleRemove() {
@@ -268121,7 +268210,7 @@ function ChannelOptions(props) {
268121
268210
  const domainTypeId = $bdb11010cef70236$export$f680877a34711e37();
268122
268211
  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" })] });
268123
268212
  }
268124
- const useStyles$c = makeStyles()(() => ({
268213
+ const useStyles$d = makeStyles()(() => ({
268125
268214
  channelSlider: {
268126
268215
  marginTop: "7px"
268127
268216
  }
@@ -268131,7 +268220,7 @@ function ChannelSlider(props) {
268131
268220
  const rgbColor = toRgbUIString(colormapOn, color2, theme);
268132
268221
  const dtype = image2?.getDtype();
268133
268222
  const fullDomain = dtype ? DOMAINS[dtype] : [0, 0];
268134
- const { classes: classes2 } = useStyles$c();
268223
+ const { classes: classes2 } = useStyles$d();
268135
268224
  useEffect(() => {
268136
268225
  if (!window2 && !disabled && Array.isArray(minMaxDomain)) {
268137
268226
  setWindow2(minMaxDomain);
@@ -268146,7 +268235,7 @@ function ChannelSlider(props) {
268146
268235
  return `${labelPrefix} for ${rgbColor} colormap channel`;
268147
268236
  }, getAriaValueText: () => `Current colormap values: ${rgbColor}-${window2}`, min: min2, max: max2, step, orientation: "horizontal", className: classes2.channelSlider, style: { color: rgbColor }, disabled });
268148
268237
  }
268149
- const useStyles$b = makeStyles()(() => ({
268238
+ const useStyles$c = makeStyles()(() => ({
268150
268239
  visibilityCheckbox: {
268151
268240
  padding: "10px 0"
268152
268241
  }
@@ -268160,10 +268249,10 @@ function getCheckboxColor(colormapOn, color2, theme) {
268160
268249
  function ChannelVisibilityCheckbox(props) {
268161
268250
  const { color: color2, visible, setVisible, disabled, theme, colormapOn } = props;
268162
268251
  const rgbColor = getCheckboxColor(colormapOn, color2, theme);
268163
- const { classes: classes2 } = useStyles$b();
268252
+ const { classes: classes2 } = useStyles$c();
268164
268253
  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" } } });
268165
268254
  }
268166
- const useStyles$a = makeStyles()(() => ({
268255
+ const useStyles$b = makeStyles()(() => ({
268167
268256
  oneLineChannelSelect: {
268168
268257
  width: "90%",
268169
268258
  marginLeft: "5%",
@@ -268171,17 +268260,23 @@ const useStyles$a = makeStyles()(() => ({
268171
268260
  }
268172
268261
  }));
268173
268262
  function ChannelSelectionDropdown(props) {
268174
- const { featureIndex, targetC, setTargetC, setWindow: setWindow2, disabled } = props;
268175
- const { classes: classes2 } = useStyles$a();
268263
+ const { featureIndex, channelsSortOrder, targetC, setTargetC, setWindow: setWindow2, disabled } = props;
268264
+ const { classes: classes2 } = useStyles$b();
268176
268265
  const { classes: selectClasses2 } = useSelectStyles();
268177
268266
  function handleChange(event2) {
268178
268267
  setTargetC(event2.target.value === "" ? null : Number(event2.target.value));
268179
268268
  setWindow2(null);
268180
268269
  }
268181
- 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) => (
268182
- // eslint-disable-next-line react/no-array-index-key
268183
- jsx$1("option", { disabled, value: channelIndex, children: channelName }, `${channelName}-${channelIndex}`)
268184
- )) }) : null;
268270
+ const sortedOptions = useMemo$1(() => {
268271
+ if (!Array.isArray(featureIndex))
268272
+ return [];
268273
+ const options = featureIndex.map((channelName, channelIndex) => ({ channelName, channelIndex }));
268274
+ if (channelsSortOrder === "alphabetical") {
268275
+ return options.sort((a2, b2) => a2.channelName.localeCompare(b2.channelName, void 0, { numeric: true, sensitivity: "base" }));
268276
+ }
268277
+ return options;
268278
+ }, [featureIndex, channelsSortOrder]);
268279
+ 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;
268185
268280
  }
268186
268281
  function ImageChannelController(props) {
268187
268282
  const {
@@ -268209,7 +268304,8 @@ function ImageChannelController(props) {
268209
268304
  spatialRenderingMode,
268210
268305
  numResolutions,
268211
268306
  spatialMaxResolution,
268212
- setSpatialMaxResolution
268307
+ setSpatialMaxResolution,
268308
+ channelsSortOrder
268213
268309
  } = props;
268214
268310
  const removeChannel = useRemoveImageChannelInMetaCoordinationScopes();
268215
268311
  const [showValueExtent, setShowValueExtent] = useState(true);
@@ -268246,7 +268342,7 @@ function ImageChannelController(props) {
268246
268342
  setWindow2(minMaxDomain);
268247
268343
  }
268248
268344
  }
268249
- 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 }) })] });
268345
+ 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 }) })] });
268250
268346
  }
268251
268347
  function DimensionSlider(props) {
268252
268348
  const { boundingSlice, sliceValue, setSliceValue, label: label2 } = props;
@@ -268300,7 +268396,7 @@ function LoadingStatusIndicator(props) {
268300
268396
  }
268301
268397
  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 }) })] });
268302
268398
  }
268303
- const useStyles$9 = makeStyles()(() => ({
268399
+ const useStyles$a = makeStyles()(() => ({
268304
268400
  imageLayerButton: {
268305
268401
  borderStyle: "dashed",
268306
268402
  marginTop: "10px",
@@ -268345,7 +268441,7 @@ const lodSliderRangeConfig = {
268345
268441
  step: 1
268346
268442
  };
268347
268443
  function ImageLayerEllipsisMenu(props) {
268348
- 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;
268444
+ 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;
268349
268445
  const [open2, setOpen] = useState(false);
268350
268446
  const { classes: selectClasses2 } = useSelectStyles();
268351
268447
  const { classes: menuClasses } = useEllipsisMenuStyles();
@@ -268371,6 +268467,9 @@ function ImageLayerEllipsisMenu(props) {
268371
268467
  function handleChannelLabelsOrientationChange(event2) {
268372
268468
  setChannelLabelsOrientation(event2.target.value);
268373
268469
  }
268470
+ function handleChannelsSortOrderChange(event2) {
268471
+ setChannelsSortOrder(event2.target.value);
268472
+ }
268374
268473
  function handleLodFactorChange(event2, value2) {
268375
268474
  setLocalLodSliderValue(value2);
268376
268475
  }
@@ -268392,11 +268491,12 @@ function ImageLayerEllipsisMenu(props) {
268392
268491
  const channelLabelsOrientationId = $bdb11010cef70236$export$f680877a34711e37();
268393
268492
  const channelLabelSizeId = $bdb11010cef70236$export$f680877a34711e37();
268394
268493
  const lodFactorId = $bdb11010cef70236$export$f680877a34711e37();
268494
+ const channelsSortOrderId = $bdb11010cef70236$export$f680877a34711e37();
268395
268495
  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: {
268396
268496
  ...lodSliderRangeConfig,
268397
268497
  inputMode: "numeric",
268398
268498
  pattern: "[0-9]*"
268399
- } })] }), 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" })] })] });
268499
+ } })] }), 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" })] })] })] });
268400
268500
  }
268401
268501
  function ImageLayerController(props) {
268402
268502
  const {
@@ -268422,8 +268522,8 @@ function ImageLayerController(props) {
268422
268522
  volumeStillRef = null
268423
268523
  } = props;
268424
268524
  const [open2, setOpen] = useState(true);
268425
- const { spatialLayerVisible: visible, spatialLayerOpacity: opacity2, spatialLayerColormap: colormap, photometricInterpretation, spatialTargetResolution, spatialLodFactor, volumetricRenderingAlgorithm, spatialLayerTransparentColor, spatialSliceX, spatialSliceY, spatialSliceZ, tooltipsVisible, spatialChannelLabelsVisible: channelLabelsVisible, spatialChannelLabelsOrientation: channelLabelsOrientation, spatialChannelLabelSize: channelLabelSize } = layerCoordination;
268426
- const { setSpatialLayerVisible: setVisible, setSpatialLayerOpacity: setOpacity2, setSpatialLayerColormap: setColormap, setPhotometricInterpretation, setSpatialTargetResolution, setSpatialLodFactor, setVolumetricRenderingAlgorithm, setSpatialLayerTransparentColor, setSpatialSliceX, setSpatialSliceY, setSpatialSliceZ, setTooltipsVisible, setSpatialChannelLabelsVisible: setChannelLabelsVisible, setSpatialChannelLabelsOrientation: setChannelLabelsOrientation, setSpatialChannelLabelSize: setChannelLabelSize } = setLayerCoordination;
268525
+ 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;
268526
+ 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;
268427
268527
  const addChannel = useAddImageChannelInMetaCoordinationScopes();
268428
268528
  const visibleSetting = typeof visible === "boolean" ? visible : true;
268429
268529
  const Visibility = useMemo$1(() => visibleSetting ? VisibilityIcon : VisibilityOffIcon, [visibleSetting]);
@@ -268439,19 +268539,19 @@ function ImageLayerController(props) {
268439
268539
  }, [visible, setVisible]);
268440
268540
  const handleOpacityChange = useCallback((e3, v) => setOpacity2(v), [setOpacity2]);
268441
268541
  const handleOpenChange = useCallback(() => setOpen((prev2) => !prev2), []);
268442
- const { classes: classes2 } = useStyles$9();
268542
+ const { classes: classes2 } = useStyles$a();
268443
268543
  const { classes: menuClasses } = useEllipsisMenuStyles();
268444
268544
  const { classes: controllerSectionClasses } = useControllerSectionStyles();
268445
268545
  const isMultiChannel = photometricInterpretation !== "RGB";
268446
- 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(ExpandMore, {}) }) : null] })] }), isMultiChannel && open2 ? jsxs(Grid$1, { container: true, direction: "column", justifyContent: "space-between", className: classes2.imageChannelControllerGrid, children: [channelScopes.map((cScope) => {
268546
+ 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(ExpandMore, {}) }) : null] })] }), isMultiChannel && open2 ? jsxs(Grid$1, { container: true, direction: "column", justifyContent: "space-between", className: classes2.imageChannelControllerGrid, children: [channelScopes.map((cScope) => {
268447
268547
  const { spatialTargetC, spatialChannelVisible, spatialChannelOpacity, spatialChannelColor, spatialChannelWindow, spatialMaxResolution } = channelCoordination[cScope];
268448
268548
  const { setSpatialTargetC, setSpatialChannelVisible, setSpatialChannelOpacity, setSpatialChannelColor, setSpatialChannelWindow, setSpatialMaxResolution } = setChannelCoordination[cScope];
268449
268549
  const channelIndex = image2?.getChannelIndex(spatialTargetC);
268450
268550
  const numResolutions = image2?.getNumResolutions();
268451
- 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);
268551
+ 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);
268452
268552
  }), 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] }) });
268453
268553
  }
268454
- const useStyles$8 = makeStyles()((theme) => ({
268554
+ const useStyles$9 = makeStyles()((theme) => ({
268455
268555
  dimensionsIcon: {
268456
268556
  height: "40px",
268457
268557
  width: "40px",
@@ -268475,7 +268575,7 @@ const useStyles$8 = makeStyles()((theme) => ({
268475
268575
  function GlobalDimensionSlider(props) {
268476
268576
  const { label: label2, targetValue, setTargetValue, min: min2 = 0, max: max2 = 0, spatialRenderingMode = null, setSpatialRenderingMode = null } = props;
268477
268577
  const { classes: lcClasses } = useControllerSectionStyles();
268478
- const { classes: classes2 } = useStyles$8();
268578
+ const { classes: classes2 } = useStyles$9();
268479
268579
  const isForZ = spatialRenderingMode !== null;
268480
268580
  const is3dMode = spatialRenderingMode === "3D";
268481
268581
  function handleRenderingModeChange(event2) {
@@ -268486,20 +268586,115 @@ function GlobalDimensionSlider(props) {
268486
268586
  }
268487
268587
  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 })] }) }) });
268488
268588
  }
268589
+ const useStyles$8 = makeStyles()(() => ({
268590
+ layerTypeCentroidSegmentationIcon: {
268591
+ height: "100%",
268592
+ marginLeft: "1px",
268593
+ fill: "currentColor",
268594
+ fontSize: "24px",
268595
+ width: "50%",
268596
+ maxWidth: "24px"
268597
+ },
268598
+ divider: {
268599
+ width: "1px",
268600
+ height: "20px",
268601
+ background: "currentColor",
268602
+ flexShrink: 0,
268603
+ margin: "0 10px"
268604
+ },
268605
+ inlineLabel: {
268606
+ margin: "0px !important",
268607
+ lineHeight: 1,
268608
+ alignSelf: "center"
268609
+ },
268610
+ iconCell: {
268611
+ display: "flex",
268612
+ alignItems: "center",
268613
+ justifyContent: "center"
268614
+ }
268615
+ }));
268616
+ function CombinedEllipsisMenu(props) {
268617
+ 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;
268618
+ const [open2, setOpen] = useState(false);
268619
+ const { classes: selectClasses2 } = useSelectStyles();
268620
+ const { classes: menuClasses } = useEllipsisMenuStyles();
268621
+ const filledId = $bdb11010cef70236$export$f680877a34711e37();
268622
+ const strokeWidthId = $bdb11010cef70236$export$f680877a34711e37();
268623
+ const colorEncodingId = $bdb11010cef70236$export$f680877a34711e37();
268624
+ const colormapId = $bdb11010cef70236$export$f680877a34711e37();
268625
+ const colormapRangeId = $bdb11010cef70236$export$f680877a34711e37();
268626
+ const tooltipsId = $bdb11010cef70236$export$f680877a34711e37();
268627
+ const crosshairsId = $bdb11010cef70236$export$f680877a34711e37();
268628
+ const legendId = $bdb11010cef70236$export$f680877a34711e37();
268629
+ 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) => {
268630
+ setFeatureValueColormap(e3.target.value);
268631
+ setPointFeatureValueColormap?.(e3.target.value);
268632
+ }, 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) => {
268633
+ setFeatureValueColormapRange(v);
268634
+ setPointFeatureValueColormapRange?.(v);
268635
+ }, 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 } } })] })] });
268636
+ }
268637
+ function SegmentationCentroidsController(props) {
268638
+ const { theme, segScope, segLayerCoordination, segChannelScopesByLayer, segChannelCoordination, pointScope, pointLayerCoordination } = props;
268639
+ const { classes: classes2 } = useStyles$8();
268640
+ const { classes: lcClasses } = useControllerSectionStyles();
268641
+ const { classes: menuClasses } = useEllipsisMenuStyles();
268642
+ const channelScopes = segChannelScopesByLayer?.[segScope] ?? [];
268643
+ const primaryChanScope = channelScopes[0];
268644
+ const chanValues = segChannelCoordination[0]?.[segScope]?.[primaryChanScope] ?? {};
268645
+ const chanSetters = segChannelCoordination[1]?.[segScope]?.[primaryChanScope] ?? {};
268646
+ const { obsType, featureType, featureValueType, spatialChannelVisible: segVisible, spatialChannelOpacity: segOpacity, spatialChannelColor: segColor, spatialSegmentationFilled: filled, spatialSegmentationStrokeWidth: strokeWidth, obsColorEncoding, featureSelection, featureValueColormap, featureValueColormapRange, tooltipsVisible, tooltipCrosshairsVisible, legendVisible } = chanValues;
268647
+ const { setSpatialChannelVisible: setSegVisible, setSpatialChannelOpacity: setSegOpacity, setSpatialChannelColor: setSegColor, setSpatialSegmentationFilled: setFilled, setSpatialSegmentationStrokeWidth: setStrokeWidth, setObsColorEncoding, setFeatureValueColormap, setFeatureValueColormapRange, setTooltipsVisible, setTooltipCrosshairsVisible, setLegendVisible } = chanSetters;
268648
+ const pointValues = pointLayerCoordination[0]?.[pointScope] ?? {};
268649
+ const pointSetters = pointLayerCoordination[1]?.[pointScope] ?? {};
268650
+ const { spatialLayerVisible: pointVisible, spatialLayerOpacity: pointOpacity, spatialLayerColor: pointColor, obsColorEncoding: pointObsColorEncoding, featureValueColormap: pointFeatureValueColormap } = pointValues;
268651
+ const { setSpatialLayerVisible: setPointVisible, setSpatialLayerOpacity: setPointOpacity, setSpatialLayerColor: setPointColor, setObsColorEncoding: setPointObsColorEncoding, setFeatureValueColormap: setPointFeatureValueColormap, setFeatureValueColormapRange: setPointFeatureValueColormapRange } = pointSetters;
268652
+ const sharedOpacity = segOpacity ?? 1;
268653
+ const handleOpacityChange = useCallback((e3, v) => {
268654
+ setSegOpacity?.(v);
268655
+ setPointOpacity?.(v);
268656
+ }, [setSegOpacity, setPointOpacity]);
268657
+ const handleSegVisibleChange = useCallback(() => {
268658
+ setSegVisible?.(typeof segVisible === "boolean" ? !segVisible : false);
268659
+ }, [segVisible, setSegVisible]);
268660
+ const handlePointVisibleChange = useCallback(() => {
268661
+ setPointVisible?.(typeof pointVisible === "boolean" ? !pointVisible : false);
268662
+ }, [pointVisible, setPointVisible]);
268663
+ const handleColorEncodingChange = useCallback((e3) => {
268664
+ const val = e3.target.value;
268665
+ setObsColorEncoding(val);
268666
+ if (val === "geneSelection") {
268667
+ setPointObsColorEncoding?.("geneSelection");
268668
+ } else if (val === "cellSetSelection") {
268669
+ setPointObsColorEncoding?.("cellSetSelection");
268670
+ } else {
268671
+ setPointObsColorEncoding?.("spatialChannelColor");
268672
+ }
268673
+ }, [setObsColorEncoding, setPointObsColorEncoding]);
268674
+ const segVisibleSetting = typeof segVisible === "boolean" ? segVisible : true;
268675
+ const pointVisibleSetting = typeof pointVisible === "boolean" ? pointVisible : true;
268676
+ const SegVisibility = segVisibleSetting ? VisibilityIcon : VisibilityOffIcon;
268677
+ const PointVisibility = pointVisibleSetting ? VisibilityIcon : VisibilityOffIcon;
268678
+ const isStaticColor = obsColorEncoding === "spatialChannelColor";
268679
+ const isColormap = obsColorEncoding === "geneSelection";
268680
+ const pointIsStaticColor = pointObsColorEncoding === "spatialChannelColor";
268681
+ const pointIsColormap = pointObsColorEncoding === "geneSelection";
268682
+ 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 }) })] }) }) });
268683
+ }
268489
268684
  function LayerController(props) {
268490
- 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;
268685
+ 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;
268491
268686
  const anyLayerHasT = Object.values(images || {}).some((image2) => image2?.image?.instance.hasTStack());
268492
268687
  const anyLayerHasZ = Object.values(images || {}).some((image2) => image2?.image?.instance.hasZStack());
268493
268688
  const maxT = Object.values(images || {}).reduce((a2, h2) => Math.max(a2, h2?.image?.instance.getNumT()), 1) - 1;
268494
268689
  const maxZ = Object.values(images || {}).reduce((a2, h2) => Math.max(a2, h2?.image?.instance.getNumZ()), 1) - 1;
268495
268690
  const reversedImageLayerScopes = useMemo$1(() => [...imageLayerScopes || []].reverse(), [imageLayerScopes]);
268496
- const reversedSegmentationLayerScopes = useMemo$1(() => [...segmentationLayerScopes || []].reverse(), [segmentationLayerScopes]);
268497
268691
  const reversedSpotLayerScopes = useMemo$1(() => [...spotLayerScopes || []].reverse(), [spotLayerScopes]);
268498
- const reversedPointLayerScopes = useMemo$1(() => [...pointLayerScopes || []].reverse(), [pointLayerScopes]);
268499
- 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))] });
268692
+ const reversedPointLayerScopes = useMemo$1(() => [...pointLayerScopes || []].filter((scope) => !pairedPointScopes.has(scope)).reverse(), [pointLayerScopes, pairedPointScopes]);
268693
+ const reversedSegmentationLayerScopes = useMemo$1(() => [...segmentationLayerScopes || []].filter((scope) => !pairedSegScopes.has(scope)).reverse(), [segmentationLayerScopes, pairedSegScopes]);
268694
+ 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))] });
268500
268695
  }
268501
268696
  function LayerControllerSubscriber(props) {
268502
- const { coordinationScopes: coordinationScopesRaw, coordinationScopesBy: coordinationScopesByRaw, closeButtonVisible, downloadButtonVisible, removeGridComponent, theme, title: title2 = "Spatial Layers", uuid: uuid2, layerPerFeatureForPoints = false, cameraPresets } = props;
268697
+ const { coordinationScopes: coordinationScopesRaw, coordinationScopesBy: coordinationScopesByRaw, closeButtonVisible, downloadButtonVisible, removeGridComponent, theme, title: title2 = "Spatial Layers", uuid: uuid2, layerPerFeatureForPoints = false, cameraPresets, helpText = ViewHelpMapping.LAYER_CONTROLLER_BETA } = props;
268503
268698
  const loaders = useLoaders();
268504
268699
  const mergeCoordination = useMergeCoordination();
268505
268700
  const coordinationScopes = useCoordinationScopes(coordinationScopesRaw);
@@ -268587,7 +268782,8 @@ function LayerControllerSubscriber(props) {
268587
268782
  CoordinationType$1.TOOLTIPS_VISIBLE,
268588
268783
  CoordinationType$1.SPATIAL_CHANNEL_LABELS_VISIBLE,
268589
268784
  CoordinationType$1.SPATIAL_CHANNEL_LABELS_ORIENTATION,
268590
- CoordinationType$1.SPATIAL_CHANNEL_LABEL_SIZE
268785
+ CoordinationType$1.SPATIAL_CHANNEL_LABEL_SIZE,
268786
+ CoordinationType$1.SPATIAL_CHANNELS_SORT_ORDER
268591
268787
  ], coordinationScopes, coordinationScopesBy, CoordinationType$1.IMAGE_LAYER);
268592
268788
  const imageChannelCoordination = useComplexCoordinationSecondary([
268593
268789
  CoordinationType$1.SPATIAL_TARGET_C,
@@ -268628,6 +268824,40 @@ function LayerControllerSubscriber(props) {
268628
268824
  CoordinationType$1.TOOLTIP_CROSSHAIRS_VISIBLE,
268629
268825
  CoordinationType$1.LEGEND_VISIBLE
268630
268826
  ], coordinationScopes, coordinationScopesBy, CoordinationType$1.POINT_LAYER);
268827
+ const centroidSegmentationPairs = React__default.useMemo(() => {
268828
+ const pairs = [];
268829
+ const pairedSegScopes = /* @__PURE__ */ new Set();
268830
+ const pairedPointScopes = /* @__PURE__ */ new Set();
268831
+ pointLayerScopes.forEach((pointScope) => {
268832
+ const pointCoord = pointLayerCoordination[0][pointScope];
268833
+ if (!pointCoord)
268834
+ return;
268835
+ const pointObsType = pointCoord[CoordinationType$1.OBS_TYPE];
268836
+ if (!pointObsType)
268837
+ return;
268838
+ const matchingSegScope = segmentationLayerScopes.find((segScope) => {
268839
+ if (pairedSegScopes.has(segScope))
268840
+ return false;
268841
+ const channelScopes = segmentationChannelScopesByLayer[segScope] || [];
268842
+ return channelScopes.some((chanScope) => {
268843
+ const chanCoord = segmentationChannelCoordination[0]?.[segScope]?.[chanScope];
268844
+ return chanCoord?.[CoordinationType$1.OBS_TYPE] === pointObsType;
268845
+ });
268846
+ });
268847
+ if (matchingSegScope) {
268848
+ pairs.push({ pointScope, segScope: matchingSegScope });
268849
+ pairedSegScopes.add(matchingSegScope);
268850
+ pairedPointScopes.add(pointScope);
268851
+ }
268852
+ });
268853
+ return { pairs, pairedSegScopes, pairedPointScopes };
268854
+ }, [
268855
+ pointLayerScopes,
268856
+ pointLayerCoordination,
268857
+ segmentationLayerScopes,
268858
+ segmentationChannelScopesByLayer,
268859
+ segmentationChannelCoordination
268860
+ ]);
268631
268861
  const [{ volumeLoadingProgress: volumeLoadingStatus, tiledPointsLoadingProgress }] = useAuxiliaryCoordination([
268632
268862
  "spatialAcceleratedVolumeLoadingProgress",
268633
268863
  "spatialTiledPointsLoadingProgress"
@@ -268655,7 +268885,7 @@ function LayerControllerSubscriber(props) {
268655
268885
  imageDataStatus,
268656
268886
  pointMultiIndicesDataStatus
268657
268887
  ]);
268658
- 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 }) });
268888
+ 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 }) });
268659
268889
  }
268660
268890
  const isIterable$2 = (obj) => Symbol.iterator in obj;
268661
268891
  const hasIterableEntries = (value2) => (
@@ -270570,7 +270800,7 @@ function HiglassGlobalStyles(props) {
270570
270800
  }
270571
270801
  register({ dataFetcher: ZarrMultivecDataFetcher_default, config: ZarrMultivecDataFetcher_default.config }, { pluginType: "dataFetcher" });
270572
270802
  const LazyHiGlassComponent = React__default.lazy(async () => {
270573
- const { HiGlassComponent } = await import("./higlass-DPgS6S1h.js");
270803
+ const { HiGlassComponent } = await import("./higlass-CKuPDg4y.js");
270574
270804
  return { default: HiGlassComponent };
270575
270805
  });
270576
270806
  const HG_SIZE = 800;
@@ -270908,6 +271138,12 @@ const useStyles$5 = makeStyles()(() => ({
270908
271138
  neuroglancerWrapper: {
270909
271139
  position: "relative",
270910
271140
  height: "100%"
271141
+ },
271142
+ meshLoadingIndicator: {
271143
+ position: "absolute",
271144
+ bottom: 8,
271145
+ left: 8,
271146
+ zIndex: 50
270911
271147
  }
270912
271148
  }));
270913
271149
  const globalNeuroglancerCss = `
@@ -272159,13 +272395,16 @@ const globalNeuroglancerStyles = {
272159
272395
  },
272160
272396
  "input.perspective-panel-show-slice-views": { pointerEvents: "all" },
272161
272397
  ".neuroglancer-rendered-data-panel": {
272162
- cursor: "crosshair",
272398
+ cursor: "grab",
272163
272399
  position: "relative",
272164
272400
  outline: "0",
272165
272401
  touchAction: "none",
272166
272402
  color: "#fff",
272167
272403
  textAlign: "left"
272168
272404
  },
272405
+ ".neuroglancer-rendered-data-panel:active": {
272406
+ cursor: "grabbing"
272407
+ },
272169
272408
  ".neuroglancer-display-dimensions-widget": {
272170
272409
  position: "absolute",
272171
272410
  cursor: "default",
@@ -273518,7 +273757,7 @@ function NeuroglancerGlobalStyles(props) {
273518
273757
  const { classes: classes2 } = props;
273519
273758
  return jsxs(Fragment, { children: [jsx$1(GlobalStyles$3, { styles: globalNeuroglancerCss }), jsx$1(ScopedGlobalStyles, { styles: globalNeuroglancerStyles, parentClassName: classes2.neuroglancerWrapper })] });
273520
273759
  }
273521
- const LazyReactNeuroglancer = React__default.lazy(() => import("./ReactNeuroglancer-BLfUOvtO.js"));
273760
+ const LazyReactNeuroglancer = React__default.lazy(() => import("./ReactNeuroglancer-C2C-vEVu.js"));
273522
273761
  function createWorker() {
273523
273762
  const worker = new WorkerFactory$1();
273524
273763
  worker.AsyncComputationWorker = WorkerFactory;
@@ -273563,12 +273802,31 @@ class NeuroglancerComp extends PureComponent {
273563
273802
  }
273564
273803
  };
273565
273804
  remapWheelToZoom(viewer.inputEventBindings.perspectiveView);
273805
+ const { getMeshIdToCellId, onViewerReady, cellsUrl } = this.props;
273566
273806
  this.prevHoverHandler = () => {
273567
- if (viewer.mouseState.pickedValue !== void 0) {
273568
- const pickedSegment = viewer.mouseState.pickedValue;
273569
- this.latestOnSelectHoveredCoords?.(pickedSegment?.low);
273807
+ const ms = viewer.mouseState;
273808
+ if (ms.pickedAnnotationId != null) {
273809
+ const byIdKey = String(ms.pickedAnnotationId);
273810
+ fetch(`${cellsUrl}/by_id/${byIdKey}`).then((r3) => r3.arrayBuffer()).then((buf) => {
273811
+ const dv = new DataView(buf);
273812
+ const actualMeshId = String(dv.getInt32(12, true));
273813
+ const cellId = getMeshIdToCellId?.(actualMeshId) ?? actualMeshId;
273814
+ this.latestOnSelectHoveredCoords?.(cellId);
273815
+ });
273816
+ return;
273570
273817
  }
273818
+ if (ms.pickedValue !== void 0 && ms.pickedValue !== null) {
273819
+ const meshId = String(ms.pickedValue?.low);
273820
+ const cellId = getMeshIdToCellId?.(meshId) ?? meshId;
273821
+ this.latestOnSelectHoveredCoords?.(cellId);
273822
+ return;
273823
+ }
273824
+ this.latestOnSelectHoveredCoords?.(null);
273571
273825
  };
273826
+ onViewerReady?.(() => {
273827
+ const panel = [...viewer.display.panels][0];
273828
+ return panel?.projectionParameters?.value_?.viewProjectionMat;
273829
+ });
273572
273830
  viewer.mouseState.changed.add(this.prevHoverHandler);
273573
273831
  } else {
273574
273832
  if (this.prevMouseStateChanged && this.prevHoverHandler) {
@@ -273589,8 +273847,8 @@ class NeuroglancerComp extends PureComponent {
273589
273847
  }
273590
273848
  }
273591
273849
  render() {
273592
- const { classes: classes2, viewerState, cellColorMapping, onLayerLoadingChange } = this.props;
273593
- 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 }) }) })] });
273850
+ const { classes: classes2, viewerState, cellColorMapping, onLayerLoadingChange, onAnnotationSourceReady, onViewerReady, meshOpacity } = this.props;
273851
+ 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 }) }) })] });
273594
273852
  }
273595
273853
  }
273596
273854
  function useMemoCustomComparison(factory2, dependencies2, customIsEqual) {
@@ -273605,17 +273863,22 @@ function useMemoCustomComparison(factory2, dependencies2, customIsEqual) {
273605
273863
  }
273606
273864
  const shallowDiff = (prevDeps, nextDeps, depName) => prevDeps[depName] !== nextDeps[depName];
273607
273865
  const shallowDiffByLayer = (prevDeps, nextDeps, depName, scopeName) => prevDeps?.[depName]?.[scopeName] !== nextDeps?.[depName]?.[scopeName];
273866
+ const shallowDiffByChannel = (prevDeps, nextDeps, depName, firstName, secondName) => prevDeps?.[depName]?.[firstName]?.[secondName] !== nextDeps?.[depName]?.[firstName]?.[secondName];
273608
273867
  const shallowDiffByChannelWithKeys = (prevDeps, nextDeps, depName, firstName, secondName, keys2) => keys2.some((k) => prevDeps?.[depName]?.[firstName]?.[secondName]?.[k] !== nextDeps?.[depName]?.[firstName]?.[secondName]?.[k]);
273609
273868
  const shallowDiffByLayerCoordinationWithKeys = (prevDeps, nextDeps, depName, layerScope, keys2) => keys2.some((k) => prevDeps?.[depName]?.[0]?.[layerScope]?.[k] !== nextDeps?.[depName]?.[0]?.[layerScope]?.[k]);
273610
273869
  const shallowDiffByChannelCoordinationWithKeys = (prevDeps, nextDeps, depName, layerScope, channelScope, keys2) => keys2.some((k) => prevDeps?.[depName]?.[0]?.[layerScope]?.[channelScope]?.[k] !== nextDeps?.[depName]?.[0]?.[layerScope]?.[channelScope]?.[k]);
273611
273870
  function customIsEqualForCellColors(prevDeps, nextDeps) {
273612
273871
  let forceUpdate = false;
273613
273872
  const curriedShallowDiff = (depName) => shallowDiff(prevDeps, nextDeps, depName);
273873
+ const curriedShallowDiffByChannel = (depName, firstName, secondName) => shallowDiffByChannel(prevDeps, nextDeps, depName, firstName, secondName);
273614
273874
  const curriedShallowDiffByChannelWithKeys = (depName, firstName, secondName, keys2) => shallowDiffByChannelWithKeys(prevDeps, nextDeps, depName, firstName, secondName, keys2);
273615
273875
  const curriedShallowDiffByChannelCoordinationWithKeys = (depName, layerScope, channelScope, keys2) => shallowDiffByChannelCoordinationWithKeys(prevDeps, nextDeps, depName, layerScope, channelScope, keys2);
273616
273876
  if (curriedShallowDiff("theme")) {
273617
273877
  forceUpdate = true;
273618
273878
  }
273879
+ if (curriedShallowDiff("csvLoaded")) {
273880
+ forceUpdate = true;
273881
+ }
273619
273882
  if (["segmentationLayerScopes", "segmentationChannelScopesByLayer"].some(curriedShallowDiff)) {
273620
273883
  forceUpdate = true;
273621
273884
  } else {
@@ -273630,8 +273893,11 @@ function customIsEqualForCellColors(prevDeps, nextDeps) {
273630
273893
  "obsSetSelection",
273631
273894
  "additionalObsSets",
273632
273895
  "spatialChannelColor",
273633
- "spatialChannelOpacity"
273634
- ])) {
273896
+ "spatialChannelOpacity",
273897
+ "featureValueColormap",
273898
+ "featureValueColormapRange",
273899
+ "featureSelection"
273900
+ ]) || curriedShallowDiffByChannel("segmentationMultiIndicesData", layerScope, channelScope) || curriedShallowDiffByChannel("segmentationMultiExpressionNormData", layerScope, channelScope)) {
273635
273901
  forceUpdate = true;
273636
273902
  }
273637
273903
  });
@@ -273677,7 +273943,13 @@ function customIsEqualForInitialViewerState(prevDeps, nextDeps) {
273677
273943
  "featureSelection",
273678
273944
  "featureFilterMode",
273679
273945
  "featureColor",
273680
- "spatialPointStrokeWidth"
273946
+ "spatialPointStrokeWidth",
273947
+ "featureValueColormap",
273948
+ "featureValueColormapRange",
273949
+ "featureSelection",
273950
+ "obsSetColor",
273951
+ "obsSetSelection",
273952
+ "additionalObsSets"
273681
273953
  ]) || Math.abs(prevDeps?.pointLayerCoordination?.[0]?.[layerScope]?.spatialLayerOpacity - nextDeps?.pointLayerCoordination?.[0]?.[layerScope]?.spatialLayerOpacity) >= 0.05) {
273682
273954
  forceUpdate = true;
273683
273955
  }
@@ -273731,6 +274003,35 @@ function getSpatialLayerColorWithSelectionShader(staticColor, opacity2, featureI
273731
274003
  }
273732
274004
  `;
273733
274005
  }
274006
+ function getObsSetSelectionFilteredShader(phenotypeColors, selectedIndices, opacity2, borderWidth = 0) {
274007
+ const numColors = phenotypeColors.length;
274008
+ const numSelected = selectedIndices.length;
274009
+ const normColors = phenotypeColors.map((c2) => normalizeColor(c2));
274010
+ const colorsDecl = `vec3 phenotypeColors[${numColors}] = vec3[${numColors}](${normColors.map((c2) => toVec3(c2)).join(", ")});`;
274011
+ const selectedDecl = `int selectedIndices[${numSelected}] = int[${numSelected}](${selectedIndices.join(", ")});`;
274012
+ return `
274013
+ void main() {
274014
+ int phenotype = prop_phenotype();
274015
+ ${colorsDecl}
274016
+ ${selectedDecl}
274017
+ bool isSelected = false;
274018
+ for (int i = 0; i < ${numSelected}; ++i) {
274019
+ if (phenotype == selectedIndices[i]) {
274020
+ isSelected = true;
274021
+ }
274022
+ }
274023
+ if (!isSelected) {
274024
+ discard;
274025
+ }
274026
+ vec3 color = vec3(0.5, 0.5, 0.5);
274027
+ if (phenotype >= 0 && phenotype < ${numColors}) {
274028
+ color = phenotypeColors[phenotype];
274029
+ }
274030
+ setColor(vec4(color, ${opacity2.toFixed(4)}));
274031
+ ${borderWidthGlsl(borderWidth)}
274032
+ }
274033
+ `;
274034
+ }
273734
274035
  function getSpatialLayerColorFilteredShader(staticColor, opacity2, featureIndices, featureIndexProp, borderWidth = 0) {
273735
274036
  const normStatic = normalizeColor(staticColor);
273736
274037
  const numFeatures = featureIndices.length;
@@ -273912,6 +274213,23 @@ function getRandomPerPointShader(opacity2, featureIndexProp, pointIndexProp, bor
273912
274213
  }
273913
274214
  `;
273914
274215
  }
274216
+ function getQuantitativeColormapShader(quantitativeColorProp, opacity2, colormap = GLSL_COLORMAP_DEFAULT, colormapRange = [0, 1], borderWidth = 0, quantitativeColorMax = 1) {
274217
+ const [low, high] = colormapRange;
274218
+ const resolvedColormap = GLSL_COLORMAPS.includes(colormap) ? colormap : GLSL_COLORMAP_DEFAULT;
274219
+ return `
274220
+ #define COLORMAP_FUNC ${resolvedColormap}
274221
+ ${colormaps$1}
274222
+ void main() {
274223
+ float rawVal = float(prop_${quantitativeColorProp}());
274224
+ float normalizedVal = rawVal / ${quantitativeColorMax.toFixed(4)};
274225
+ float t = (normalizedVal - ${low.toFixed(4)}) / max(${(high - low).toFixed(4)}, 0.0001);
274226
+ t = clamp(t, 0.0, 1.0);
274227
+ vec4 col = COLORMAP_FUNC(t);
274228
+ setColor(vec4(col.rgb, ${opacity2.toFixed(4)}));
274229
+ ${borderWidthGlsl(borderWidth)}
274230
+ }
274231
+ `;
274232
+ }
273915
274233
  function getRandomPerPointWithSelectionShader(featureIndices, defaultColor, opacity2, featureIndexProp, pointIndexProp, borderWidth = 0) {
273916
274234
  const normDefault = normalizeColor(defaultColor);
273917
274235
  const numFeatures = featureIndices.length;
@@ -273967,8 +274285,25 @@ function getRandomPerPointFilteredShader(featureIndices, opacity2, featureIndexP
273967
274285
  }
273968
274286
  `;
273969
274287
  }
274288
+ function getObsSetSelectionShader(phenotypeColors, opacity2, borderWidth = 0) {
274289
+ const numColors = phenotypeColors.length;
274290
+ const normColors = phenotypeColors.map((c2) => normalizeColor(c2));
274291
+ const colorsDecl = `vec3 phenotypeColors[${numColors}] = vec3[${numColors}](${normColors.map((c2) => toVec3(c2)).join(", ")});`;
274292
+ return `
274293
+ void main() {
274294
+ int phenotype = prop_phenotype();
274295
+ ${colorsDecl}
274296
+ vec3 color = ${toVec3(normalizeColor([128, 128, 128]))};
274297
+ if (phenotype >= 0 && phenotype < ${numColors}) {
274298
+ color = phenotypeColors[phenotype];
274299
+ }
274300
+ setColor(vec4(color, ${opacity2.toFixed(4)}));
274301
+ ${borderWidthGlsl(borderWidth)}
274302
+ }
274303
+ `;
274304
+ }
273970
274305
  function getPointsShader(layerCoordination) {
273971
- const { theme, featureIndex, spatialLayerOpacity, obsColorEncoding, spatialLayerColor, featureSelection, featureFilterMode, featureColor, pointMarkerBorderWidth = 0, featureIndexProp, pointIndexProp } = layerCoordination;
274306
+ const { theme, featureIndex, spatialLayerOpacity, obsColorEncoding, spatialLayerColor, featureSelection, featureFilterMode, featureColor, pointMarkerBorderWidth = 0, featureIndexProp, pointIndexProp, quantitativeColorProp, featureValueColormap, featureValueColormapRange, obsSetColor, obsSetSelection, quantitativeColorMax, pointsAreSegmentationCentroids } = layerCoordination;
273972
274307
  const defaultColor = getDefaultColor(theme);
273973
274308
  const opacity2 = spatialLayerOpacity ?? 1;
273974
274309
  const staticColor = Array.isArray(spatialLayerColor) && spatialLayerColor.length === 3 ? spatialLayerColor : defaultColor;
@@ -273992,7 +274327,7 @@ function getPointsShader(layerCoordination) {
273992
274327
  }
273993
274328
  return getSpatialLayerColorWithSelectionShader(staticColor, opacity2, featureIndices, defaultColor, featureIndexProp, pointMarkerBorderWidth);
273994
274329
  }
273995
- if (obsColorEncoding === "geneSelection") {
274330
+ if (obsColorEncoding === "geneSelection" && !pointsAreSegmentationCentroids) {
273996
274331
  if (!featureIndexProp) {
273997
274332
  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.");
273998
274333
  }
@@ -274028,19 +274363,49 @@ function getPointsShader(layerCoordination) {
274028
274363
  }
274029
274364
  return getRandomPerPointWithSelectionShader(featureIndices, defaultColor, opacity2, featureIndexProp, pointIndexProp, pointMarkerBorderWidth);
274030
274365
  }
274366
+ if (obsColorEncoding === "geneSelection" && pointsAreSegmentationCentroids) {
274367
+ if (!quantitativeColorProp) {
274368
+ 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.");
274369
+ return getSpatialLayerColorShader(staticColor, opacity2, pointMarkerBorderWidth);
274370
+ }
274371
+ return getQuantitativeColormapShader(quantitativeColorProp, opacity2, featureValueColormap, featureValueColormapRange, pointMarkerBorderWidth, quantitativeColorMax);
274372
+ }
274373
+ if (obsColorEncoding === "cellSetSelection") {
274374
+ if (!obsSetColor || !obsSetSelection) {
274375
+ return getSpatialLayerColorShader(staticColor, opacity2, pointMarkerBorderWidth);
274376
+ }
274377
+ const colorByName = {};
274378
+ obsSetColor?.forEach(({ path: path2, color: color2 }) => {
274379
+ if (path2?.length > 1) {
274380
+ colorByName[path2[path2.length - 1]] = color2;
274381
+ }
274382
+ });
274383
+ const uniqueNames = Object.keys(colorByName).sort();
274384
+ const phenotypeColors = uniqueNames.map((name2) => colorByName[name2] ?? defaultColor);
274385
+ if (isFiltered) {
274386
+ const selectedNames = new Set(obsSetSelection?.map((path2) => path2[path2.length - 1]) ?? []);
274387
+ const selectedIndices = uniqueNames.map((name2, idx) => selectedNames.has(name2) ? idx : -1).filter((idx) => idx >= 0);
274388
+ if (selectedIndices.length === 0) {
274389
+ return "void main() { discard; }";
274390
+ }
274391
+ return getObsSetSelectionFilteredShader(phenotypeColors, selectedIndices, opacity2, pointMarkerBorderWidth);
274392
+ }
274393
+ return getObsSetSelectionShader(phenotypeColors, opacity2, pointMarkerBorderWidth);
274394
+ }
274031
274395
  return getSpatialLayerColorShader(staticColor, opacity2, pointMarkerBorderWidth);
274032
274396
  }
274397
+ const DEFAULT_NG_DIMENSIONS = {
274398
+ x: [1, "nm"],
274399
+ y: [1, "nm"],
274400
+ z: [1, "nm"]
274401
+ };
274033
274402
  const DEFAULT_NG_PROPS = {
274034
274403
  layout: "3d",
274035
274404
  position: [0, 0, 0],
274036
274405
  projectionOrientation: [0, 0, 0, 1],
274037
274406
  projectionScale: 1024,
274038
274407
  crossSectionScale: 1,
274039
- dimensions: {
274040
- x: [1, "nm"],
274041
- y: [1, "nm"],
274042
- z: [1, "nm"]
274043
- },
274408
+ dimensions: DEFAULT_NG_DIMENSIONS,
274044
274409
  layers: []
274045
274410
  };
274046
274411
  function toPrecomputedSource(url) {
@@ -274058,6 +274423,15 @@ function toNgLayerName(dataType, layerScope, channelScope = null) {
274058
274423
  }
274059
274424
  throw new Error(`Unsupported data type: ${dataType}`);
274060
274425
  }
274426
+ function pointsHaveMatchingSegmentation({ pointObsType, segmentationLayerScopes, segmentationChannelScopesByLayer, segmentationChannelCoordination }) {
274427
+ return segmentationLayerScopes.some((segmentationLayerScope) => {
274428
+ const segmentationChannelScopes = segmentationChannelScopesByLayer?.[segmentationLayerScope] || [];
274429
+ return segmentationChannelScopes.some((segmentationChannelScope) => {
274430
+ const segmentationObsType = segmentationChannelCoordination?.[0]?.[segmentationLayerScope]?.[segmentationChannelScope]?.obsType;
274431
+ return segmentationObsType && pointObsType && segmentationObsType === pointObsType;
274432
+ });
274433
+ });
274434
+ }
274061
274435
  function useNeuroglancerViewerState(theme, showAxisLines, segmentationLayerScopes, segmentationChannelScopesByLayer, segmentationLayerCoordination, segmentationChannelCoordination, obsSegmentationsUrls, obsSegmentationsData, pointLayerScopes, pointLayerCoordination, obsPointsUrls, obsPointsData, pointMultiIndicesData) {
274062
274436
  const viewerState = useMemoCustomComparison(() => {
274063
274437
  let result = cloneDeep$1(DEFAULT_NG_PROPS);
@@ -274080,6 +274454,9 @@ function useNeuroglancerViewerState(theme, showAxisLines, segmentationLayerScope
274080
274454
  } : toPrecomputedSource(layerUrl);
274081
274455
  result = {
274082
274456
  ...result,
274457
+ showAxisLines,
274458
+ showDefaultAnnotations: false,
274459
+ // this removes the yellow box around pointsLayer
274083
274460
  layers: [
274084
274461
  ...result.layers,
274085
274462
  {
@@ -274103,9 +274480,27 @@ function useNeuroglancerViewerState(theme, showAxisLines, segmentationLayerScope
274103
274480
  const layerCoordination = pointLayerCoordination[0][layerScope];
274104
274481
  const layerData = obsPointsData[layerScope];
274105
274482
  const layerUrl = obsPointsUrls[layerScope]?.[0]?.url;
274483
+ const ngOptions = layerData?.neuroglancerOptions;
274484
+ if (ngOptions?.transform?.matrix && ngOptions?.transform?.outputDimensions) {
274485
+ result = {
274486
+ ...result,
274487
+ dimensions: ngOptions.transform.outputDimensions ?? DEFAULT_NG_DIMENSIONS
274488
+ };
274489
+ }
274490
+ const pointsAreSegmentationCentroids = pointsHaveMatchingSegmentation({
274491
+ pointObsType: layerCoordination?.obsType,
274492
+ segmentationLayerScopes,
274493
+ segmentationChannelScopesByLayer,
274494
+ segmentationChannelCoordination
274495
+ });
274106
274496
  const featureIndex = pointMultiIndicesData[layerScope]?.featureIndex;
274107
274497
  if (layerUrl && layerData) {
274108
- const { spatialLayerVisible, spatialLayerOpacity, obsColorEncoding, spatialLayerColor, featureSelection, featureFilterMode, featureColor, spatialPointStrokeWidth } = layerCoordination || {};
274498
+ const { spatialLayerVisible, spatialLayerOpacity, obsColorEncoding, spatialLayerColor, featureSelection, featureFilterMode, featureColor, spatialPointStrokeWidth, featureValueColormap, featureValueColormapRange } = layerCoordination || {};
274499
+ const quantitativeColorMax = layerData.neuroglancerOptions?.quantitativeColorMax;
274500
+ if (!quantitativeColorMax && obsColorEncoding === "geneSelection") {
274501
+ console.warn("quantitativeColorMax not specified in options — defaulting to 1.0 (no normalization)");
274502
+ }
274503
+ const quantitativeColorMaxSelected = quantitativeColorMax ?? 1;
274109
274504
  const shader = getPointsShader({
274110
274505
  theme,
274111
274506
  featureIndex,
@@ -274115,9 +274510,18 @@ function useNeuroglancerViewerState(theme, showAxisLines, segmentationLayerScope
274115
274510
  featureSelection,
274116
274511
  featureFilterMode,
274117
274512
  featureColor,
274513
+ featureValueColormap,
274514
+ featureValueColormapRange,
274118
274515
  featureIndexProp: layerData.neuroglancerOptions?.featureIndexProp,
274119
274516
  pointIndexProp: layerData.neuroglancerOptions?.pointIndexProp,
274120
- pointMarkerBorderWidth: spatialPointStrokeWidth ?? 0
274517
+ quantitativeColorProp: layerData.neuroglancerOptions?.quantitativeColorProp,
274518
+ pointMarkerBorderWidth: spatialPointStrokeWidth ?? 0,
274519
+ obsSets: layerCoordination.obsSets,
274520
+ obsSetColor: layerCoordination.obsSetColor,
274521
+ obsSetSelection: layerCoordination.obsSetSelection,
274522
+ additionalObsSets: layerCoordination.additionalObsSets,
274523
+ quantitativeColorMax: quantitativeColorMaxSelected,
274524
+ pointsAreSegmentationCentroids
274121
274525
  });
274122
274526
  result = {
274123
274527
  ...result,
@@ -274130,7 +274534,13 @@ function useNeuroglancerViewerState(theme, showAxisLines, segmentationLayerScope
274130
274534
  subsources: {
274131
274535
  default: true
274132
274536
  },
274133
- enableDefaultSubsources: false
274537
+ enableDefaultSubsources: false,
274538
+ ...ngOptions?.transform?.matrix ? {
274539
+ transform: {
274540
+ matrix: ngOptions.transform.matrix,
274541
+ outputDimensions: ngOptions.transform?.outputDimensions ?? result.dimensions
274542
+ }
274543
+ } : {}
274134
274544
  },
274135
274545
  tab: "annotations",
274136
274546
  shader,
@@ -274138,7 +274548,7 @@ function useNeuroglancerViewerState(theme, showAxisLines, segmentationLayerScope
274138
274548
  visible: spatialLayerVisible,
274139
274549
  // Options from layerData.neuroglancerOptions
274140
274550
  // like projectionAnnotationSpacing:
274141
- projectionAnnotationSpacing: layerData.neuroglancerOptions?.projectionAnnotationSpacing ?? 1
274551
+ projectionAnnotationSpacing: ngOptions?.projectionAnnotationSpacing ?? 1
274142
274552
  }
274143
274553
  ],
274144
274554
  // TODO: is this needed?
@@ -274169,6 +274579,8 @@ function useNeuroglancerViewerState(theme, showAxisLines, segmentationLayerScope
274169
274579
  }, customIsEqualForInitialViewerState);
274170
274580
  return viewerState;
274171
274581
  }
274582
+ const ANNOTATION_HEADER_OFFSET = 8;
274583
+ const GREY_HEX = "#323232";
274172
274584
  const EPSILON_KEYS_MAPPING_NG = {
274173
274585
  projectionScale: 100,
274174
274586
  projectionOrientation: 0.02,
@@ -274189,6 +274601,17 @@ const multiplyQuat = (a2, b2) => {
274189
274601
  const conjQuat = (q) => [-q[0], -q[1], -q[2], q[3]];
274190
274602
  const rad2deg = (r3) => r3 * 180 / Math.PI;
274191
274603
  const deg2rad = (d) => d * Math.PI / 180;
274604
+ function applyColormap(colormap, t4) {
274605
+ const tClamped = Math.max(0, Math.min(1, t4));
274606
+ const interpolators = {
274607
+ plasma: interpolatePlasma,
274608
+ viridis: interpolateViridis,
274609
+ jet: interpolateJet,
274610
+ greys: interpolateGreys
274611
+ };
274612
+ const fn2 = interpolators[colormap] ?? interpolateViridis;
274613
+ return fn2(tClamped);
274614
+ }
274192
274615
  function valueGreaterThanEpsilon(a2, b2, epsilon3) {
274193
274616
  if (Array.isArray(a2) && Array.isArray(b2) && a2.length === b2.length) {
274194
274617
  return a2.some((val, i2) => Math.abs(val - b2[i2]) > epsilon3);
@@ -274255,13 +274678,57 @@ function makeVitNgZoomCalibrator(initialNgProjectionScale, initialDeckZoom = 0)
274255
274678
  }
274256
274679
  };
274257
274680
  }
274681
+ function parseAnnotationChunkSegmentsWithPositions(buffer2, serializer2) {
274682
+ const dv = new DataView(buffer2);
274683
+ if (buffer2.byteLength <= 8)
274684
+ return [];
274685
+ const count2 = dv.getUint32(0, true);
274686
+ if (count2 === 0)
274687
+ return [];
274688
+ const numBytes = serializer2.serializedBytes;
274689
+ const properties2 = [null, null, null, null, null];
274690
+ const results = [];
274691
+ for (let i2 = 0; i2 < count2; i2++) {
274692
+ serializer2.deserialize(dv, ANNOTATION_HEADER_OFFSET, i2, count2, true, properties2);
274693
+ const phenotype = properties2[0];
274694
+ const segId = properties2[1];
274695
+ if (segId > 0) {
274696
+ const propBase = ANNOTATION_HEADER_OFFSET + i2 * numBytes;
274697
+ const x2 = dv.getFloat32(propBase + 0, true);
274698
+ const y2 = dv.getFloat32(propBase + 4, true);
274699
+ const z2 = dv.getFloat32(propBase + 8, true);
274700
+ if (x2 !== 0 || y2 !== 0 || z2 !== 0) {
274701
+ results.push({
274702
+ id: String(segId),
274703
+ // mesh segment ID - use for segments array
274704
+ phenotype: Number(phenotype),
274705
+ x: x2,
274706
+ y: y2,
274707
+ z: z2
274708
+ });
274709
+ }
274710
+ }
274711
+ }
274712
+ return results;
274713
+ }
274714
+ const remapCellColors = (ngCellColors, cellIdToMeshIdRef) => {
274715
+ const remapped = {};
274716
+ Object.entries(ngCellColors).forEach(([cellId, color2]) => {
274717
+ const meshId = cellIdToMeshIdRef.current[cellId] ?? cellId;
274718
+ remapped[meshId] = color2;
274719
+ });
274720
+ return remapped;
274721
+ };
274258
274722
  const VITESSCE_INTERACTION_DELAY = 50;
274259
274723
  const INIT_VIT_ZOOM = -3.6;
274260
274724
  const ZOOM_EPS = 0.01;
274261
274725
  const ROTATION_EPS = 1e-3;
274262
274726
  const TARGET_EPS = 0.5;
274263
274727
  const NG_ROT_COOLDOWN_MS = 120;
274728
+ const MESH_LOAD_THRESHOLD = 100;
274729
+ const MESH_LOADING_OVERLAY_TIMEOUT = 1500;
274264
274730
  const GUIDE_URL = "https://vitessce.io/docs/ng-guide/";
274731
+ const MESH_OPACITY = 0.6;
274265
274732
  const LAST_INTERACTION_SOURCE = {
274266
274733
  vitessce: "vitessce",
274267
274734
  neuroglancer: "neuroglancer"
@@ -274278,10 +274745,11 @@ function NeuroglancerSubscriber(props) {
274278
274745
  downloadButtonVisible,
274279
274746
  removeGridComponent,
274280
274747
  theme,
274281
- showAxisLines = false,
274748
+ showAxisLines = true,
274282
274749
  title: title2 = "Spatial",
274283
274750
  subtitle = "Powered by Neuroglancer",
274284
274751
  helpText = ViewHelpMapping.NEUROGLANCER,
274752
+ meshLoadProjectionScaleThreshold,
274285
274753
  // Note: this is a temporary mechanism
274286
274754
  // to pass an initial NG camera state.
274287
274755
  // Ideally, all camera state should be passed via
@@ -274292,6 +274760,32 @@ function NeuroglancerSubscriber(props) {
274292
274760
  } = props;
274293
274761
  const loaders = useLoaders();
274294
274762
  const mergeCoordination = useMergeCoordination();
274763
+ const { classes: classes2 } = useStyles$5();
274764
+ const initialRotationPushedRef = useRef(false);
274765
+ const getViewProjectionMatRef = useRef(null);
274766
+ const obsIdToMeshIdRef = useRef({});
274767
+ const meshIdToCellIdRef = useRef({});
274768
+ const cellIdToMeshIdRef = useRef({});
274769
+ const ngRotPushAtRef = useRef(0);
274770
+ const lastInteractionSource = useRef(null);
274771
+ const applyNgUpdateTimeoutRef = useRef(null);
274772
+ const lastNgPushOrientationRef = useRef(null);
274773
+ const initialRenderCalibratorRef = useRef(null);
274774
+ const translationOffsetRef = useRef([0, 0, 0]);
274775
+ const zoomRafRef = useRef(null);
274776
+ const lastNgQuatRef = useRef([0, 0, 0, 1]);
274777
+ const lastNgScaleRef = useRef(null);
274778
+ const annotationInfoRef = useRef(null);
274779
+ const annotationTransformRef = useRef(null);
274780
+ const visibleSegmentIdsRef = useRef(null);
274781
+ const chunkCacheRef = useRef(/* @__PURE__ */ new Map());
274782
+ const resizeObserverRef = useRef(null);
274783
+ const [isLayersLoaded, setIsLayersLoaded] = useState(false);
274784
+ const [isMeshLoading, setIsMeshLoading] = useState(false);
274785
+ const [annotationReady, setAnnotationReady] = useState(false);
274786
+ const [csvLoaded, setCsvLoaded] = useState(false);
274787
+ const updateVisibleSegmentsThrottledRef = useRef(null);
274788
+ const viewportSizeRef = useRef({ width: 0, height: 0 });
274295
274789
  const coordinationScopes = useCoordinationScopes(coordinationScopesRaw);
274296
274790
  const coordinationScopesBy = useCoordinationScopesBy(coordinationScopes, coordinationScopesByRaw);
274297
274791
  const [{
@@ -274323,6 +274817,19 @@ function NeuroglancerSubscriber(props) {
274323
274817
  setSpatialRotationOrbit: setRotationOrbit,
274324
274818
  setSpatialZoom: setZoom
274325
274819
  }] = useCoordination(COMPONENT_COORDINATION_TYPES[ViewType$1.NEUROGLANCER], coordinationScopes);
274820
+ const csvUrlRef = useRef(null);
274821
+ const csvUrl = useMemo$1(() => {
274822
+ if (csvUrlRef.current)
274823
+ return csvUrlRef.current;
274824
+ const obsSetsMap = loaders?.[dataset]?.loaders?.obsSets;
274825
+ if (!obsSetsMap)
274826
+ return null;
274827
+ const firstLoader = obsSetsMap.values().next().value;
274828
+ const url = firstLoader?.url ?? null;
274829
+ if (url)
274830
+ csvUrlRef.current = url;
274831
+ return url;
274832
+ }, [loaders, dataset]);
274326
274833
  const [ngWidth, ngHeight, containerRef] = useGridItemSize();
274327
274834
  const [segmentationLayerScopes, segmentationChannelScopesByLayer] = useMultiCoordinationScopesSecondaryNonNull(CoordinationType$1.SEGMENTATION_CHANNEL, CoordinationType$1.SEGMENTATION_LAYER, coordinationScopes, coordinationScopesBy);
274328
274835
  const pointLayerScopes = useMultiCoordinationScopesNonNull(CoordinationType$1.POINT_LAYER, coordinationScopes);
@@ -274351,7 +274858,9 @@ function NeuroglancerSubscriber(props) {
274351
274858
  CoordinationType$1.OBS_HIGHLIGHT,
274352
274859
  CoordinationType$1.TOOLTIPS_VISIBLE,
274353
274860
  CoordinationType$1.TOOLTIP_CROSSHAIRS_VISIBLE,
274354
- CoordinationType$1.LEGEND_VISIBLE
274861
+ CoordinationType$1.LEGEND_VISIBLE,
274862
+ CoordinationType$1.FEATURE_TYPE,
274863
+ CoordinationType$1.FEATURE_VALUE_TYPE
274355
274864
  ], coordinationScopes, coordinationScopesBy, CoordinationType$1.SEGMENTATION_LAYER, CoordinationType$1.SEGMENTATION_CHANNEL);
274356
274865
  const pointLayerCoordination = useComplexCoordination([
274357
274866
  CoordinationType$1.OBS_TYPE,
@@ -274368,7 +274877,10 @@ function NeuroglancerSubscriber(props) {
274368
274877
  CoordinationType$1.TOOLTIPS_VISIBLE,
274369
274878
  CoordinationType$1.TOOLTIP_CROSSHAIRS_VISIBLE,
274370
274879
  CoordinationType$1.LEGEND_VISIBLE,
274371
- CoordinationType$1.SPATIAL_POINT_STROKE_WIDTH
274880
+ CoordinationType$1.SPATIAL_POINT_STROKE_WIDTH,
274881
+ CoordinationType$1.OBS_SET_COLOR,
274882
+ CoordinationType$1.OBS_SET_SELECTION,
274883
+ CoordinationType$1.ADDITIONAL_OBS_SETS
274372
274884
  ], coordinationScopes, coordinationScopesBy, CoordinationType$1.POINT_LAYER);
274373
274885
  const [obsPointsData, obsPointsDataStatus, obsPointsUrls, obsPointsErrors] = useMultiObsPoints(coordinationScopes, coordinationScopesBy, loaders, dataset, mergeCoordination, uuid2);
274374
274886
  const [pointMultiIndicesData, pointMultiIndicesDataStatus, pointMultiIndicesDataErrors] = usePointMultiObsFeatureMatrixIndices(coordinationScopes, coordinationScopesBy, loaders, dataset);
@@ -274394,34 +274906,89 @@ function NeuroglancerSubscriber(props) {
274394
274906
  segmentationMultiFeatureSelectionStatus,
274395
274907
  segmentationMultiIndicesDataStatus
274396
274908
  ]);
274397
- const { classes: classes2 } = useStyles$5();
274909
+ const lastVitessceRotationRef = useRef({
274910
+ x: spatialRotationX,
274911
+ y: spatialRotationY,
274912
+ z: spatialRotationZ,
274913
+ orbit: spatialRotationOrbit
274914
+ });
274915
+ const prevCoordsRef = useRef({
274916
+ zoom: spatialZoom,
274917
+ rx: spatialRotationX,
274918
+ ry: spatialRotationY,
274919
+ rz: spatialRotationZ,
274920
+ orbit: spatialRotationOrbit,
274921
+ tx: spatialTargetX,
274922
+ ty: spatialTargetY
274923
+ });
274398
274924
  const segmentationColorMapping = useMemoCustomComparison(() => {
274399
274925
  const result = {};
274400
274926
  segmentationLayerScopes?.forEach((layerScope) => {
274401
274927
  result[layerScope] = {};
274402
274928
  segmentationChannelScopesByLayer?.[layerScope]?.forEach((channelScope) => {
274403
- const { obsSets: layerSets, obsIndex: layerIndex } = obsSegmentationsSetsData?.[layerScope]?.[channelScope] || {};
274404
- const { obsSetColor, obsColorEncoding, obsSetSelection, additionalObsSets, spatialChannelColor, spatialChannelOpacity } = segmentationChannelCoordination[0][layerScope][channelScope];
274929
+ const { obsSets: layerSets, obsIndex: layerIndexFromSets } = obsSegmentationsSetsData?.[layerScope]?.[channelScope] || {};
274930
+ const layerIndex = layerIndexFromSets ?? null;
274931
+ const { obsSetColor, obsColorEncoding, obsSetSelection, additionalObsSets, spatialChannelColor, spatialChannelOpacity, featureValueColormap, featureValueColormapRange } = segmentationChannelCoordination[0][layerScope][channelScope];
274405
274932
  if (obsColorEncoding === "spatialChannelColor") {
274406
- if (layerIndex && spatialChannelColor) {
274933
+ if (spatialChannelColor) {
274407
274934
  const hex2 = rgbToHex$1(spatialChannelColor);
274408
274935
  const ngCellColors = {};
274409
- if (obsSetSelection?.length > 0) {
274410
- const mergedCellSets = mergeObsSets(layerSets, additionalObsSets);
274411
- const selectedIds = /* @__PURE__ */ new Set();
274412
- obsSetSelection.forEach((setPath) => {
274413
- const rootNode = mergedCellSets?.tree?.find((n3) => n3.name === setPath[0]);
274414
- const leafNode = setPath.length > 1 ? rootNode?.children?.find((n3) => n3.name === setPath[1]) : rootNode;
274415
- leafNode?.set?.forEach(([id2]) => selectedIds.add(String(id2)));
274416
- });
274417
- layerIndex.forEach((id2) => {
274418
- if (selectedIds.has(String(id2))) {
274936
+ if (layerIndex) {
274937
+ if (obsSetSelection?.length > 0) {
274938
+ const mergedCellSets = mergeObsSets(layerSets, additionalObsSets);
274939
+ const selectedIds = /* @__PURE__ */ new Set();
274940
+ obsSetSelection.forEach((setPath) => {
274941
+ const rootNode = mergedCellSets?.tree?.find((n3) => n3.name === setPath[0]);
274942
+ const leafNode = setPath.length > 1 ? rootNode?.children?.find((n3) => n3.name === setPath[1]) : rootNode;
274943
+ leafNode?.set?.forEach(([id2]) => selectedIds.add(String(id2)));
274944
+ });
274945
+ layerIndex.forEach((id2) => {
274946
+ if (selectedIds.has(String(id2))) {
274947
+ ngCellColors[id2] = hex2;
274948
+ }
274949
+ });
274950
+ } else {
274951
+ layerIndex.forEach((id2) => {
274419
274952
  ngCellColors[id2] = hex2;
274420
- }
274421
- });
274953
+ });
274954
+ }
274422
274955
  }
274423
- result[layerScope][channelScope] = ngCellColors;
274956
+ result[layerScope][channelScope] = remapCellColors(ngCellColors, cellIdToMeshIdRef);
274957
+ result[layerScope].opacity = spatialChannelOpacity ?? 1;
274958
+ result[layerScope].defaultColor = hex2;
274959
+ }
274960
+ } else if (obsColorEncoding === "geneSelection") {
274961
+ const instanceObsIndex = obsSegmentationsSetsData?.[layerScope]?.[channelScope]?.obsIndex;
274962
+ const matrixObsIndex = segmentationMultiIndicesData?.[layerScope]?.[channelScope]?.obsIndex;
274963
+ const expressionData = segmentationMultiExpressionNormData?.[layerScope]?.[channelScope];
274964
+ if (instanceObsIndex && matrixObsIndex && expressionData?.[0]) {
274965
+ const matrixIndexMap = new Map(matrixObsIndex.map((key2, i2) => {
274966
+ const normalizedKey = key2.replace(/^.*_/, "");
274967
+ return [normalizedKey, i2];
274968
+ }));
274969
+ const toMatrixIndex = instanceObsIndex.map((key2) => matrixIndexMap.get(String(key2)));
274970
+ const [low, high] = featureValueColormapRange ?? [0, 1];
274971
+ const ngCellColors = {};
274972
+ instanceObsIndex.forEach((id2, i2) => {
274973
+ const rowIndex = toMatrixIndex[i2];
274974
+ const rawVal = expressionData[0][rowIndex] ?? 0;
274975
+ const t4 = rawVal / 255;
274976
+ const tScaled = (t4 - low) / Math.max(high - low, 1e-4);
274977
+ const tClamped = Math.max(0, Math.min(1, tScaled));
274978
+ const color2 = applyColormap(featureValueColormap ?? "viridis", tClamped);
274979
+ ngCellColors[id2] = rgbToHex$1(color2);
274980
+ });
274981
+ result[layerScope][channelScope] = remapCellColors(ngCellColors, cellIdToMeshIdRef);
274982
+ result[layerScope].opacity = spatialChannelOpacity ?? 1;
274983
+ } else if (instanceObsIndex) {
274984
+ const fallbackColor = spatialChannelColor ? rgbToHex$1(spatialChannelColor) : GREY_HEX;
274985
+ const ngCellColors = {};
274986
+ instanceObsIndex.forEach((id2) => {
274987
+ ngCellColors[id2] = fallbackColor;
274988
+ });
274989
+ result[layerScope][channelScope] = remapCellColors(ngCellColors, cellIdToMeshIdRef);
274424
274990
  result[layerScope].opacity = spatialChannelOpacity ?? 1;
274991
+ result[layerScope].defaultColor = fallbackColor;
274425
274992
  }
274426
274993
  } else if (layerSets && layerIndex) {
274427
274994
  const mergedCellSets = mergeObsSets(layerSets, additionalObsSets);
@@ -274433,10 +275000,10 @@ function NeuroglancerSubscriber(props) {
274433
275000
  theme
274434
275001
  });
274435
275002
  const ngCellColors = {};
274436
- cellColors.forEach((color2, i2) => {
274437
- ngCellColors[i2] = rgbToHex$1(color2);
275003
+ cellColors.forEach((color2, id2) => {
275004
+ ngCellColors[id2] = rgbToHex$1(color2);
274438
275005
  });
274439
- result[layerScope][channelScope] = ngCellColors;
275006
+ result[layerScope][channelScope] = remapCellColors(ngCellColors, cellIdToMeshIdRef);
274440
275007
  result[layerScope].opacity = spatialChannelOpacity ?? 1;
274441
275008
  }
274442
275009
  });
@@ -274449,14 +275016,201 @@ function NeuroglancerSubscriber(props) {
274449
275016
  segmentationChannelScopesByLayer,
274450
275017
  obsSegmentationsSetsData,
274451
275018
  segmentationChannelCoordination,
274452
- theme
275019
+ theme,
275020
+ segmentationMultiExpressionNormData,
275021
+ segmentationMultiIndicesData,
275022
+ csvLoaded
274453
275023
  }, customIsEqualForCellColors);
274454
- const initalViewerState = useNeuroglancerViewerState(theme, showAxisLines, segmentationLayerScopes, segmentationChannelScopesByLayer, segmentationLayerCoordination, segmentationChannelCoordination, obsSegmentationsUrls, obsSegmentationsData, pointLayerScopes, pointLayerCoordination, obsPointsUrls, obsPointsData, pointMultiIndicesData);
275024
+ const initialViewerState = useNeuroglancerViewerState(theme, showAxisLines, segmentationLayerScopes, segmentationChannelScopesByLayer, segmentationLayerCoordination, segmentationChannelCoordination, obsSegmentationsUrls, obsSegmentationsData, pointLayerScopes, pointLayerCoordination, obsPointsUrls, obsPointsData, pointMultiIndicesData);
274455
275025
  const [latestViewerStateIteration, incrementLatestViewerStateIteration] = useReducer((x2) => x2 + 1, 0);
274456
275026
  const latestViewerStateRef = useRef({
274457
- ...initalViewerState,
275027
+ ...initialViewerState,
274458
275028
  ...initialNgCameraState ?? {}
274459
275029
  });
275030
+ useEffect(() => {
275031
+ if (!csvUrl)
275032
+ return;
275033
+ fetch(csvUrl).then((r3) => r3.text()).then((text2) => {
275034
+ const lines = text2.split("\n");
275035
+ const header = lines[0].split(",");
275036
+ const meshIdIdx = header.indexOf("MeshID");
275037
+ const cellIdIdx = header.indexOf("CellID");
275038
+ const meshMap = {};
275039
+ const cellMap = {};
275040
+ lines.slice(1).forEach((l2) => {
275041
+ const cols = l2.split(",");
275042
+ const meshId = cols[meshIdIdx]?.trim();
275043
+ const cellId = cols[cellIdIdx]?.trim();
275044
+ if (meshId && cellId) {
275045
+ meshMap[meshId] = cellId;
275046
+ cellMap[cellId] = meshId;
275047
+ }
275048
+ });
275049
+ meshIdToCellIdRef.current = meshMap;
275050
+ cellIdToMeshIdRef.current = cellMap;
275051
+ setCsvLoaded(true);
275052
+ });
275053
+ }, [csvUrl]);
275054
+ useEffect(() => {
275055
+ chunkCacheRef.current.clear();
275056
+ }, [obsPointsData]);
275057
+ useEffect(() => {
275058
+ if (ngWidth && ngHeight) {
275059
+ viewportSizeRef.current = { width: ngWidth, height: ngHeight };
275060
+ }
275061
+ }, [ngWidth, ngHeight]);
275062
+ const setContainerNode = useCallback((node2) => {
275063
+ containerRef.current = node2;
275064
+ if (resizeObserverRef.current) {
275065
+ resizeObserverRef.current.disconnect();
275066
+ resizeObserverRef.current = null;
275067
+ }
275068
+ if (node2) {
275069
+ const rect2 = node2.getBoundingClientRect();
275070
+ if (rect2.width && rect2.height) {
275071
+ viewportSizeRef.current = { width: rect2.width, height: rect2.height };
275072
+ }
275073
+ const observer2 = new ResizeObserver((entries2) => {
275074
+ const entry2 = entries2[0];
275075
+ if (!entry2)
275076
+ return;
275077
+ const { width: width2, height: height2 } = entry2.contentRect;
275078
+ if (width2 && height2) {
275079
+ viewportSizeRef.current = { width: width2, height: height2 };
275080
+ }
275081
+ });
275082
+ observer2.observe(node2);
275083
+ resizeObserverRef.current = observer2;
275084
+ }
275085
+ }, []);
275086
+ const updateVisibleSegments = useCallback(async () => {
275087
+ if (!annotationInfoRef.current)
275088
+ return;
275089
+ if (!annotationTransformRef.current)
275090
+ return;
275091
+ if (!segmentationLayerScopes?.length)
275092
+ return;
275093
+ if (!pointLayerScopes?.length)
275094
+ return;
275095
+ const { position: position2, projectionScale } = latestViewerStateRef.current;
275096
+ if (!position2 || !projectionScale)
275097
+ return;
275098
+ const maxProjectionScale = meshLoadProjectionScaleThreshold ?? MESH_LOAD_THRESHOLD;
275099
+ if (projectionScale > maxProjectionScale) {
275100
+ if (visibleSegmentIdsRef.current?.length !== 0) {
275101
+ visibleSegmentIdsRef.current = [];
275102
+ incrementLatestViewerStateIteration();
275103
+ setIsMeshLoading(false);
275104
+ }
275105
+ return;
275106
+ }
275107
+ const { width: width2, height: height2 } = viewportSizeRef.current;
275108
+ if (!width2 || !height2)
275109
+ return;
275110
+ const transform3 = annotationTransformRef.current;
275111
+ const info2 = annotationInfoRef.current;
275112
+ const cellsUrl2 = info2.url;
275113
+ const allLevelCoords = info2.spatial.flatMap((level) => {
275114
+ const [gx, gy, gz] = level.grid_shape;
275115
+ const coords = [];
275116
+ for (let cx = 0; cx < gx; cx++) {
275117
+ for (let cy = 0; cy < gy; cy++) {
275118
+ for (let cz = 0; cz < gz; cz++) {
275119
+ coords.push({ level: level.key, cx, cy, cz });
275120
+ }
275121
+ }
275122
+ }
275123
+ return coords;
275124
+ });
275125
+ const fetchChunkWithPositions = async ({ level, cx, cy, cz }) => {
275126
+ const { serializers, serializer: defaultSerializer } = annotationTransformRef.current;
275127
+ const serializer2 = serializers?.[0] ?? defaultSerializer;
275128
+ if (!serializer2)
275129
+ return [];
275130
+ const cacheKey = `${cellsUrl2}/${level}/${cx}_${cy}_${cz}`;
275131
+ if (chunkCacheRef.current.has(cacheKey)) {
275132
+ return chunkCacheRef.current.get(cacheKey);
275133
+ }
275134
+ try {
275135
+ const res = await fetch(cacheKey);
275136
+ if (!res.ok) {
275137
+ chunkCacheRef.current.set(cacheKey, []);
275138
+ return [];
275139
+ }
275140
+ const buffer2 = await res.arrayBuffer();
275141
+ const entries2 = parseAnnotationChunkSegmentsWithPositions(buffer2, serializer2);
275142
+ chunkCacheRef.current.set(cacheKey, entries2);
275143
+ return entries2;
275144
+ } catch (e3) {
275145
+ chunkCacheRef.current.set(cacheKey, []);
275146
+ return [];
275147
+ }
275148
+ };
275149
+ try {
275150
+ const results = await Promise.all(allLevelCoords.map(fetchChunkWithPositions));
275151
+ const seenIds = /* @__PURE__ */ new Set();
275152
+ const allEntries = results.flat().filter(({ id: id2 }) => {
275153
+ if (seenIds.has(id2))
275154
+ return false;
275155
+ seenIds.add(id2);
275156
+ return true;
275157
+ });
275158
+ const obsIdToMeshId = {};
275159
+ allEntries.forEach(({ id: id2, obsId }) => {
275160
+ obsIdToMeshId[obsId] = id2;
275161
+ });
275162
+ obsIdToMeshIdRef.current = obsIdToMeshId;
275163
+ const mat = getViewProjectionMatRef.current?.();
275164
+ let visibleIds;
275165
+ if (!mat) {
275166
+ console.warn("No viewProjectionMatrix, loading all");
275167
+ visibleIds = [...new Set(allEntries.map(({ id: id2 }) => id2))];
275168
+ } else {
275169
+ const margin2 = Math.max(width2, height2) * 0.5;
275170
+ visibleIds = [...new Set(allEntries.filter(({ x: x2, y: y2, z: z2 }) => {
275171
+ const vx = x2 / transform3.x;
275172
+ const vy = y2 / transform3.y;
275173
+ const vz = (z2 || 0) / transform3.z;
275174
+ const cx = mat[0] * vx + mat[4] * vy + mat[8] * vz + mat[12];
275175
+ const cy = mat[1] * vx + mat[5] * vy + mat[9] * vz + mat[13];
275176
+ const cw2 = mat[3] * vx + mat[7] * vy + mat[11] * vz + mat[15];
275177
+ const screenX = (cx / cw2 + 1) * 0.5 * width2;
275178
+ const screenY = (1 - cy / cw2) * 0.5 * height2;
275179
+ return screenX >= -margin2 && screenX <= width2 + margin2 && screenY >= -margin2 && screenY <= height2 + margin2;
275180
+ }).map(({ id: id2 }) => id2))];
275181
+ }
275182
+ if (visibleIds.length === 0) {
275183
+ if (visibleSegmentIdsRef.current?.length !== 0) {
275184
+ visibleSegmentIdsRef.current = [];
275185
+ incrementLatestViewerStateIteration();
275186
+ }
275187
+ setIsMeshLoading(false);
275188
+ return;
275189
+ }
275190
+ const prevIds = new Set(visibleSegmentIdsRef.current ?? []);
275191
+ const addedIdsCount = visibleIds.filter((id2) => !prevIds.has(id2)).length;
275192
+ const hasSignificantChange = addedIdsCount > 20;
275193
+ if (hasSignificantChange && visibleIds.length > 0) {
275194
+ setIsMeshLoading(true);
275195
+ }
275196
+ const prevSorted = [...visibleSegmentIdsRef.current ?? []].sort().join(",");
275197
+ const nextSorted = [...visibleIds].sort().join(",");
275198
+ if (prevSorted !== nextSorted) {
275199
+ visibleSegmentIdsRef.current = visibleIds;
275200
+ incrementLatestViewerStateIteration();
275201
+ }
275202
+ if (hasSignificantChange) {
275203
+ setTimeout(() => setIsMeshLoading(false), MESH_LOADING_OVERLAY_TIMEOUT);
275204
+ }
275205
+ } catch (e3) {
275206
+ console.warn("[updateVisibleSegments] error:", e3);
275207
+ setIsMeshLoading(false);
275208
+ }
275209
+ }, [segmentationLayerScopes, pointLayerScopes, obsPointsData, meshLoadProjectionScaleThreshold]);
275210
+ useEffect(() => {
275211
+ updateVisibleSegmentsThrottledRef.current = throttle$1(updateVisibleSegments, 500);
275212
+ return () => updateVisibleSegmentsThrottledRef.current?.cancel();
275213
+ }, [updateVisibleSegments]);
274460
275214
  useEffect(() => {
274461
275215
  const prevNgCameraState = {
274462
275216
  position: latestViewerStateRef.current.position,
@@ -274464,37 +275218,50 @@ function NeuroglancerSubscriber(props) {
274464
275218
  projectionScale: latestViewerStateRef.current.projectionScale
274465
275219
  };
274466
275220
  latestViewerStateRef.current = {
274467
- ...initalViewerState,
275221
+ ...initialViewerState,
274468
275222
  ...prevNgCameraState
274469
275223
  };
274470
275224
  incrementLatestViewerStateIteration();
274471
- }, [initalViewerState]);
274472
- const initialRotationPushedRef = useRef(false);
274473
- const ngRotPushAtRef = useRef(0);
274474
- const lastInteractionSource = useRef(null);
274475
- const applyNgUpdateTimeoutRef = useRef(null);
274476
- const lastNgPushOrientationRef = useRef(null);
274477
- const initialRenderCalibratorRef = useRef(null);
274478
- const translationOffsetRef = useRef([0, 0, 0]);
274479
- const zoomRafRef = useRef(null);
274480
- const lastNgQuatRef = useRef([0, 0, 0, 1]);
274481
- const lastNgScaleRef = useRef(null);
274482
- const lastVitessceRotationRef = useRef({
274483
- x: spatialRotationX,
274484
- y: spatialRotationY,
274485
- z: spatialRotationZ,
274486
- orbit: spatialRotationOrbit
274487
- });
274488
- const [isLayersLoaded, setIsLayersLoaded] = useState(false);
274489
- const prevCoordsRef = useRef({
274490
- zoom: spatialZoom,
274491
- rx: spatialRotationX,
274492
- ry: spatialRotationY,
274493
- rz: spatialRotationZ,
274494
- orbit: spatialRotationOrbit,
274495
- tx: spatialTargetX,
274496
- ty: spatialTargetY
274497
- });
275225
+ }, [initialViewerState]);
275226
+ const cellsUrl = useMemo$1(() => {
275227
+ const firstScope = pointLayerScopes?.[0];
275228
+ return obsPointsUrls?.[firstScope]?.[0]?.url ?? null;
275229
+ }, [pointLayerScopes, obsPointsUrls]);
275230
+ const hasMatchingAnnotationSource = useMemo$1(() => {
275231
+ if (!cellsUrl)
275232
+ return false;
275233
+ const firstPointScope = pointLayerScopes?.[0];
275234
+ return pointsHaveMatchingSegmentation({
275235
+ pointObsType: pointLayerCoordination[0]?.[firstPointScope]?.obsType,
275236
+ segmentationLayerScopes,
275237
+ segmentationChannelScopesByLayer,
275238
+ segmentationChannelCoordination
275239
+ });
275240
+ }, [cellsUrl, pointLayerScopes, segmentationLayerScopes, segmentationChannelScopesByLayer]);
275241
+ useEffect(() => {
275242
+ if (!cellsUrl)
275243
+ return;
275244
+ fetch(`${cellsUrl}/info`).then((r3) => r3.json()).then((info2) => {
275245
+ const infoWithUrl = {
275246
+ ...info2,
275247
+ url: cellsUrl
275248
+ };
275249
+ annotationInfoRef.current = infoWithUrl;
275250
+ if (annotationTransformRef.current)
275251
+ setAnnotationReady(true);
275252
+ }).catch((err2) => console.error("failed to fetch annotation info:", err2));
275253
+ }, [cellsUrl]);
275254
+ useEffect(() => {
275255
+ if (annotationReady) {
275256
+ setIsLayersLoaded(true);
275257
+ updateVisibleSegments();
275258
+ }
275259
+ }, [annotationReady]);
275260
+ const onAnnotationSourceReady = useCallback((transform3) => {
275261
+ annotationTransformRef.current = transform3;
275262
+ if (annotationInfoRef.current)
275263
+ setAnnotationReady(true);
275264
+ }, []);
274498
275265
  const handleStateUpdate = useCallback((newState) => {
274499
275266
  lastInteractionSource.current = LAST_INTERACTION_SOURCE.neuroglancer;
274500
275267
  const { projectionScale, projectionOrientation, position: position2 } = newState;
@@ -274527,6 +275294,7 @@ function NeuroglancerSubscriber(props) {
274527
275294
  setZoom(vitZoomFromNg);
274528
275295
  zoomRafRef.current = null;
274529
275296
  });
275297
+ updateVisibleSegments();
274530
275298
  }
274531
275299
  lastNgScaleRef.current = projectionScale;
274532
275300
  }
@@ -274567,7 +275335,8 @@ function NeuroglancerSubscriber(props) {
274567
275335
  projectionScale,
274568
275336
  position: position2
274569
275337
  };
274570
- }, []);
275338
+ updateVisibleSegmentsThrottledRef.current?.();
275339
+ }, [updateVisibleSegmentsThrottledRef]);
274571
275340
  const onSegmentClick = useCallback((value2) => {
274572
275341
  if (value2) {
274573
275342
  const id2 = String(value2);
@@ -274592,11 +275361,18 @@ function NeuroglancerSubscriber(props) {
274592
275361
  const channelScope = segmentationChannelScopesByLayer?.[layerScope]?.[0];
274593
275362
  result[layerScope] = {
274594
275363
  colors: segmentationColorMapping?.[layerScope]?.[channelScope] ?? {},
274595
- opacity: segmentationColorMapping?.[layerScope]?.opacity ?? 1
275364
+ opacity: segmentationColorMapping?.[layerScope]?.opacity ?? 1,
275365
+ defaultColor: segmentationColorMapping?.[layerScope]?.defaultColor ?? null
274596
275366
  };
274597
275367
  });
274598
275368
  return result;
274599
275369
  }, [segmentationColorMapping, segmentationLayerScopes, segmentationChannelScopesByLayer]);
275370
+ useEffect(() => {
275371
+ if (!hasMatchingAnnotationSource && isReady && !segmentationLayerScopes?.length) {
275372
+ setIsLayersLoaded(true);
275373
+ }
275374
+ }, [hasMatchingAnnotationSource, isReady, segmentationLayerScopes]);
275375
+ const meshOpacity = hasMatchingAnnotationSource ? MESH_OPACITY : void 0;
274600
275376
  const derivedViewerState = useMemo$1(() => {
274601
275377
  const { current: current2 } = latestViewerStateRef;
274602
275378
  if (current2.layers.length <= 0) {
@@ -274655,13 +275431,33 @@ function NeuroglancerSubscriber(props) {
274655
275431
  lastInteractionSource.current = null;
274656
275432
  }
274657
275433
  const updatedLayers = current2?.layers?.map((layer, idx) => {
274658
- const layerScope = segmentationLayerScopes?.[idx];
275434
+ if (layer.type !== "segmentation")
275435
+ return layer;
275436
+ const layerScope = segmentationLayerScopes?.find((scope) => layer.name?.includes(scope));
275437
+ if (!layerScope)
275438
+ return layer;
274659
275439
  const layerColorMapping = cellColorMappingByLayer?.[layerScope]?.colors ?? {};
274660
- const layerSegments = Object.keys(layerColorMapping);
275440
+ const defaultColor = cellColorMappingByLayer?.[layerScope]?.defaultColor;
275441
+ let segments2 = [];
275442
+ if (hasMatchingAnnotationSource) {
275443
+ segments2 = visibleSegmentIdsRef.current ?? [];
275444
+ } else if (Object.keys(layerColorMapping).length > 0) {
275445
+ segments2 = Object.keys(layerColorMapping);
275446
+ }
275447
+ let derivedSegmentColors = {};
275448
+ if (hasMatchingAnnotationSource && segments2.length > 0) {
275449
+ segments2.forEach((meshId) => {
275450
+ const color2 = layerColorMapping[meshId] || defaultColor;
275451
+ if (color2)
275452
+ derivedSegmentColors[meshId] = color2;
275453
+ });
275454
+ } else if (!hasMatchingAnnotationSource) {
275455
+ derivedSegmentColors = layerColorMapping;
275456
+ }
274661
275457
  return {
274662
275458
  ...layer,
274663
- segments: layerSegments,
274664
- segmentColors: layerColorMapping,
275459
+ segments: segments2,
275460
+ segmentColors: derivedSegmentColors,
274665
275461
  objectAlpha: cellColorMappingByLayer?.[layerScope]?.opacity ?? 1
274666
275462
  };
274667
275463
  }) ?? [];
@@ -274691,17 +275487,21 @@ function NeuroglancerSubscriber(props) {
274691
275487
  spatialRotationZ,
274692
275488
  spatialTargetX,
274693
275489
  spatialTargetY,
274694
- initalViewerState,
274695
- latestViewerStateIteration
275490
+ initialViewerState,
275491
+ latestViewerStateIteration,
275492
+ hasMatchingAnnotationSource
274696
275493
  ]);
274697
275494
  const onSegmentHighlight = useCallback((obsId) => {
274698
275495
  setCellHighlight(String(obsId));
274699
275496
  }, [setCellHighlight]);
274700
275497
  const handleLayerLoadingChange = useCallback((isLoaded) => {
274701
- setIsLayersLoaded(isLoaded);
274702
- }, []);
275498
+ if (!isLayersLoaded && isLoaded) {
275499
+ setIsLayersLoaded(true);
275500
+ }
275501
+ }, [isLayersLoaded]);
274703
275502
  const hasLayers = derivedViewerState?.layers?.length > 0;
274704
- 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, {
275503
+ const showPointsLegend = !hasMatchingAnnotationSource;
275504
+ 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, {
274705
275505
  theme: "dark",
274706
275506
  maxHeight: ngHeight,
274707
275507
  // Segmentations
@@ -274710,10 +275510,12 @@ function NeuroglancerSubscriber(props) {
274710
275510
  segmentationChannelScopesByLayer,
274711
275511
  segmentationChannelCoordination,
274712
275512
  // Points
274713
- pointLayerScopes,
275513
+ pointLayerScopes: showPointsLegend ? pointLayerScopes : void 0,
274714
275514
  pointLayerCoordination,
274715
275515
  pointMultiIndicesData
274716
- }) }), jsx$1(NeuroglancerComp, { classes: classes2, onSegmentClick, onSelectHoveredCoords: onSegmentHighlight, viewerState: derivedViewerState, cellColorMapping: cellColorMappingByLayer, setViewerState: handleStateUpdate, onLayerLoadingChange: handleLayerLoadingChange })] }) : null });
275516
+ }) }), 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) => {
275517
+ getViewProjectionMatRef.current = getFn;
275518
+ }, getMeshIdToCellId: (meshId) => meshIdToCellIdRef.current[meshId], cellsUrl, meshOpacity })] }) : null });
274717
275519
  }
274718
275520
  const FEATURE_AGGREGATION_STRATEGIES = ["first", "last", "sum", "mean"];
274719
275521
  function CellSetExpressionPlotOptions(props) {
@@ -276552,7 +277354,7 @@ function useFilteredVolcanoData(props) {
276552
277354
  const rawSampleSetPathB = coordinationValues.sampleSetFilter[1];
276553
277355
  const sampleSetPathB = [...rawSampleSetPathB];
276554
277356
  sampleSetPathB[0] = sampleSetsColumnNameMappingReversed[rawSampleSetPathB[0]];
276555
- if (isEqual$3(sampleSetPathA, sampleSetSelection[1]) && isEqual$3(sampleSetPathB, sampleSetSelection[0])) {
277357
+ if (isEqual$3(sampleSetPathA, sampleSetSelection[0]) && isEqual$3(sampleSetPathB, sampleSetSelection[1])) {
276556
277358
  shouldSwapFoldChangeDirection = true;
276557
277359
  }
276558
277360
  }
@@ -327211,7 +328013,7 @@ const VEGA_THEMES = {
327211
328013
  };
327212
328014
  const DATASET_NAME = "table";
327213
328015
  function VegaGlobalStyles() {
327214
- const { theme } = useStyles$y();
328016
+ const { theme } = useStyles$z();
327215
328017
  return jsx$1(GlobalStyles$3, { styles: {
327216
328018
  "#vg-tooltip-element.vg-tooltip.custom-theme": {
327217
328019
  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)",
@@ -327245,7 +328047,7 @@ function renderTooltipContents(tooltipText) {
327245
328047
  }
327246
328048
  function VegaPlot(props) {
327247
328049
  const { spec: partialSpec, data: data2, getTooltipText, signalListeners, renderer = "svg", onNewView } = props;
327248
- const { classes: tooltipClasses2 } = useStyles$t();
328050
+ const { classes: tooltipClasses2 } = useStyles$u();
327249
328051
  const tooltipHandler = useMemo$1(() => {
327250
328052
  if (typeof getTooltipText === "function") {
327251
328053
  const tooltipConfig = {
@@ -328306,6 +329108,10 @@ function TreemapSubscriber(props) {
328306
329108
  }, [setObsSetSelection]);
328307
329109
  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 })] });
328308
329110
  }
329111
+ const GOLDEN_RATIO = 0.6180339887498948;
329112
+ function topJitter(index2, rangePixels) {
329113
+ return index2 * GOLDEN_RATIO % 1 * rangePixels;
329114
+ }
328309
329115
  function VolcanoPlot(props) {
328310
329116
  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;
328311
329117
  const svgRef = useRef();
@@ -328367,10 +329173,10 @@ function VolcanoPlot(props) {
328367
329173
  const obsSetPath2 = [...rawObsSetPath];
328368
329174
  obsSetPath2[0] = obsSetsColumnNameMappingReversed[rawObsSetPath[0]];
328369
329175
  const color2 = obsSetColorScale(obsSetPath2);
328370
- 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) => {
329176
+ 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) => {
328371
329177
  onFeatureClick(d.featureId);
328372
329178
  });
328373
- 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) => {
329179
+ 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) => {
328374
329180
  onFeatureClick(d.featureId);
328375
329181
  });
328376
329182
  textElements.append("title").text((d) => `${featureType}: ${d.featureId}
@@ -353180,6 +353986,7 @@ const baseCoordinationTypes = [
353180
353986
  new PluginCoordinationType(CoordinationType$1.SPATIAL_CHANNEL_LABELS_VISIBLE, true, z.boolean()),
353181
353987
  new PluginCoordinationType(CoordinationType$1.SPATIAL_CHANNEL_LABELS_ORIENTATION, "vertical", z.enum(["vertical", "horizontal"])),
353182
353988
  new PluginCoordinationType(CoordinationType$1.SPATIAL_CHANNEL_LABEL_SIZE, 14, z.number()),
353989
+ new PluginCoordinationType(CoordinationType$1.SPATIAL_CHANNELS_SORT_ORDER, "original", z.enum(["original", "alphabetical"])),
353183
353990
  new PluginCoordinationType(CoordinationType$1.SAMPLE_TYPE, "sample", z.string().nullable()),
353184
353991
  // TODO: remove one array level and use multi-coordination for sampleSetSelection?
353185
353992
  new PluginCoordinationType(CoordinationType$1.SAMPLE_FILTER, null, z.array(z.string()).nullable()),
@@ -353734,7 +354541,7 @@ function UncontrolledLauncher(props) {
353734
354541
  ));
353735
354542
  }
353736
354543
  export {
353737
- diffCameraState as $,
354544
+ GREY_HEX as $,
353738
354545
  require_descriptors as A,
353739
354546
  BaseDecoder as B,
353740
354547
  CoordinationType$1 as C,
@@ -353763,8 +354570,9 @@ export {
353763
354570
  require_redefine as Z,
353764
354571
  require_objectAssign as _,
353765
354572
  getDefaultExportFromCjs$1 as a,
353766
- UncontrolledLauncher as a0,
353767
- ControlledLauncher as a1,
354573
+ diffCameraState as a0,
354574
+ UncontrolledLauncher as a1,
354575
+ ControlledLauncher as a2,
353768
354576
  LercAddCompression as b,
353769
354577
  commonjsGlobal$1 as c,
353770
354578
  requireAssign as d,