@vitessce/launcher 3.8.13 → 3.9.1

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.
@@ -1,6 +1,6 @@
1
1
  import * as React from "react";
2
2
  import React__default, { useState, useEffect, useCallback, useContext, forwardRef as forwardRef$1, useRef, useMemo as useMemo$1, createContext, createElement, isValidElement, cloneElement, Children, version as version$6, useLayoutEffect as useLayoutEffect$1, useImperativeHandle, PureComponent, Component as Component$1, Suspense, useReducer } from "react";
3
- import { useLoaders, useCoordinationScopes, useCoordination, useDescription, useImageData, useReady, TitleInfo, useVitessceContainer, useSetWarning, useObsSetsData, useUrls, usePlotOptionsStyles, OptionsContainer, CellColorEncodingOption, OptionSelect, useComponentHover, useComponentViewInfo, useSetComponentHover, useSetComponentViewInfo, useInitialCoordination, useDeckCanvasSize, useMultiObsLabels, useObsEmbeddingData, useFeatureSelection, useObsFeatureMatrixIndices, useFeatureLabelsData, useExpandedFeatureLabelsMap, useSampleSetsData, useSampleEdgesData, useGetObsInfo, useUint8FeatureSelection, useExpressionValueGetter, useAuxiliaryCoordination, useHasLoader, useObsLocationsData, useObsLabelsData, useObsSegmentationsData, useNeighborhoodsData, useMergeCoordination, useCoordinationScopesBy, useMultiCoordinationScopesSecondaryNonNull, useMultiCoordinationScopesNonNull, useComplexCoordination, useComplexCoordinationSecondary, useGridItemSize, useMultiObsPoints, usePointMultiObsLabels, useMultiObsSpots, useSpotMultiObsSets, useSpotMultiFeatureLabels, useSpotMultiFeatureSelection, useSpotMultiObsFeatureMatrixIndices, useSegmentationMultiObsLocations, useMultiObsSegmentations, useSegmentationMultiObsSets, useSegmentationMultiFeatureSelection, useSegmentationMultiObsFeatureMatrixIndices, useMultiImages, useObsFeatureMatrixData, useUint8ObsFeatureMatrix, useGetObsMembership, PopperMenu, useComponentLayout, useClosestVitessceContainerSize, useWindowDimensions, useRemoveImageChannelInMetaCoordinationScopes, useAddImageChannelInMetaCoordinationScopes, useViewConfigStoreApi, useViewConfig, useSetViewConfig, createLoaders, useWarning, useGenomicProfilesData, useMatchingLoader, useColumnNameMapping, useFeatureStatsData, useObsSetStatsData, useAsyncFunction, useFeatureSetStatsData, useComparisonMetadata, logConfig, VitSContainer, createOnDrop } from "@vitessce/vit-s";
3
+ import { useLoaders, useCoordinationScopes, useCoordination, useDescription, useImageData, useReady, TitleInfo, useVitessceContainer, useSetWarning, useObsSetsData, useUrls, usePlotOptionsStyles, OptionsContainer, CellColorEncodingOption, OptionSelect, useComponentHover, useComponentViewInfo, useSetComponentHover, useSetComponentViewInfo, useInitialCoordination, useDeckCanvasSize, useMultiObsLabels, useObsEmbeddingData, useFeatureSelection, useObsFeatureMatrixIndices, useFeatureLabelsData, useExpandedFeatureLabelsMap, useSampleSetsData, useSampleEdgesData, useGetObsInfo, useUint8FeatureSelection, useExpressionValueGetter, useAuxiliaryCoordination, useHasLoader, useObsLocationsData, useObsLabelsData, useObsSegmentationsData, useNeighborhoodsData, useMergeCoordination, useCoordinationScopesBy, useMultiCoordinationScopesSecondaryNonNull, useMultiCoordinationScopesNonNull, useComplexCoordination, useComplexCoordinationSecondary, useGridItemSize, useMultiObsPoints, usePointMultiObsLabels, usePointMultiObsFeatureMatrixIndices, useMultiObsSpots, useSpotMultiObsSets, useSpotMultiFeatureLabels, useSpotMultiFeatureSelection, useSpotMultiObsFeatureMatrixIndices, useSegmentationMultiObsLocations, useMultiObsSegmentations, useSegmentationMultiObsSets, useSegmentationMultiFeatureSelection, useSegmentationMultiObsFeatureMatrixIndices, useMultiImages, useObsFeatureMatrixData, useUint8ObsFeatureMatrix, useGetObsMembership, PopperMenu, useComponentLayout, useClosestVitessceContainerSize, useWindowDimensions, useRemoveImageChannelInMetaCoordinationScopes, useAddImageChannelInMetaCoordinationScopes, useViewConfigStoreApi, useViewConfig, useSetViewConfig, createLoaders, useWarning, useGenomicProfilesData, useMatchingLoader, useColumnNameMapping, useFeatureStatsData, useObsSetStatsData, useAsyncFunction, useFeatureSetStatsData, useComparisonMetadata, logConfig, VitSContainer, createOnDrop } from "@vitessce/vit-s";
4
4
  import * as ReactDOM from "react-dom";
5
5
  import ReactDOM__default, { createPortal } from "react-dom";
6
6
  function _mergeNamespaces(n3, m2) {
@@ -11197,6 +11197,7 @@ const CoordinationType$1 = {
11197
11197
  OBS_SET_HIGHLIGHT: "obsSetHighlight",
11198
11198
  OBS_SET_EXPANSION: "obsSetExpansion",
11199
11199
  OBS_SET_COLOR: "obsSetColor",
11200
+ FEATURE_COLOR: "featureColor",
11200
11201
  FEATURE_HIGHLIGHT: "featureHighlight",
11201
11202
  FEATURE_SELECTION: "featureSelection",
11202
11203
  FEATURE_SET_SELECTION: "featureSetSelection",
@@ -11801,6 +11802,8 @@ const COMPONENT_COORDINATION_TYPES = {
11801
11802
  CoordinationType$1.OBS_SET_FILTER,
11802
11803
  CoordinationType$1.OBS_SET_HIGHLIGHT,
11803
11804
  CoordinationType$1.OBS_SET_COLOR,
11805
+ CoordinationType$1.FEATURE_COLOR,
11806
+ CoordinationType$1.FEATURE_FILTER_MODE,
11804
11807
  CoordinationType$1.FEATURE_HIGHLIGHT,
11805
11808
  CoordinationType$1.FEATURE_SELECTION,
11806
11809
  CoordinationType$1.FEATURE_VALUE_COLORMAP,
@@ -12015,6 +12018,8 @@ const COMPONENT_COORDINATION_TYPES = {
12015
12018
  CoordinationType$1.SPATIAL_SPOT_STROKE_WIDTH,
12016
12019
  CoordinationType$1.SPATIAL_LAYER_COLOR,
12017
12020
  CoordinationType$1.OBS_COLOR_ENCODING,
12021
+ CoordinationType$1.FEATURE_COLOR,
12022
+ CoordinationType$1.FEATURE_FILTER_MODE,
12018
12023
  CoordinationType$1.FEATURE_VALUE_COLORMAP,
12019
12024
  CoordinationType$1.FEATURE_VALUE_COLORMAP_RANGE,
12020
12025
  CoordinationType$1.FEATURE_SELECTION,
@@ -12653,7 +12658,9 @@ const obsSegmentationsSpatialdataSchema = z.object({
12653
12658
  const obsPointsSpatialdataSchema = z.object({
12654
12659
  path: z.string().describe("The path to the point data."),
12655
12660
  tablePath: z.string().optional().describe("The path to a table which annotates the points. If available but not specified, the spot identifiers may not be aligned with associated tabular data as expected."),
12656
- coordinateSystem: z.string().optional().describe('The name of a coordinate transformation output used to transform the image. If not provided, the "global" coordinate system is assumed.')
12661
+ coordinateSystem: z.string().optional().describe('The name of a coordinate transformation output used to transform the image. If not provided, the "global" coordinate system is assumed.'),
12662
+ featureIndexColumn: z.string().optional().describe("The name of the column in the table which contains the feature (e.g., gene) indices associated with each point (aligned with the table var.index dataframe column)."),
12663
+ mortonCodeColumn: z.string().optional().describe('The name of the column in the table which contains the Morton codes for each point, used for efficient spatial querying. If not provided, Vitessce will assume the default column name "morton_code_2d".')
12657
12664
  });
12658
12665
  z.object({
12659
12666
  path: z.string(),
@@ -33315,7 +33322,7 @@ var _span$3;
33315
33322
  const useUtilityClasses$1j = (ownerState) => {
33316
33323
  const {
33317
33324
  classes: classes2,
33318
- contained,
33325
+ contained: contained2,
33319
33326
  size: size2,
33320
33327
  disabled,
33321
33328
  error: error2,
@@ -33324,7 +33331,7 @@ const useUtilityClasses$1j = (ownerState) => {
33324
33331
  required: required2
33325
33332
  } = ownerState;
33326
33333
  const slots = {
33327
- root: ["root", disabled && "disabled", error2 && "error", size2 && `size${capitalize$1(size2)}`, contained && "contained", focused && "focused", filled && "filled", required2 && "required"]
33334
+ root: ["root", disabled && "disabled", error2 && "error", size2 && `size${capitalize$1(size2)}`, contained2 && "contained", focused && "focused", filled && "filled", required2 && "required"]
33328
33335
  };
33329
33336
  return composeClasses(slots, getFormHelperTextUtilityClasses, classes2);
33330
33337
  };
@@ -34817,6 +34824,90 @@ ListItemText.propTypes = {
34817
34824
  */
34818
34825
  sx: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), PropTypes.func, PropTypes.object])
34819
34826
  };
34827
+ function getListItemIconUtilityClass(slot) {
34828
+ return generateUtilityClass("MuiListItemIcon", slot);
34829
+ }
34830
+ const listItemIconClasses = generateUtilityClasses("MuiListItemIcon", ["root", "alignItemsFlexStart"]);
34831
+ const useUtilityClasses$1d = (ownerState) => {
34832
+ const {
34833
+ alignItems,
34834
+ classes: classes2
34835
+ } = ownerState;
34836
+ const slots = {
34837
+ root: ["root", alignItems === "flex-start" && "alignItemsFlexStart"]
34838
+ };
34839
+ return composeClasses(slots, getListItemIconUtilityClass, classes2);
34840
+ };
34841
+ const ListItemIconRoot = styled("div", {
34842
+ name: "MuiListItemIcon",
34843
+ slot: "Root",
34844
+ overridesResolver: (props, styles2) => {
34845
+ const {
34846
+ ownerState
34847
+ } = props;
34848
+ return [styles2.root, ownerState.alignItems === "flex-start" && styles2.alignItemsFlexStart];
34849
+ }
34850
+ })(memoTheme(({
34851
+ theme
34852
+ }) => ({
34853
+ minWidth: 56,
34854
+ color: (theme.vars || theme).palette.action.active,
34855
+ flexShrink: 0,
34856
+ display: "inline-flex",
34857
+ variants: [{
34858
+ props: {
34859
+ alignItems: "flex-start"
34860
+ },
34861
+ style: {
34862
+ marginTop: 8
34863
+ }
34864
+ }]
34865
+ })));
34866
+ const ListItemIcon = /* @__PURE__ */ React.forwardRef(function ListItemIcon2(inProps, ref2) {
34867
+ const props = useDefaultProps({
34868
+ props: inProps,
34869
+ name: "MuiListItemIcon"
34870
+ });
34871
+ const {
34872
+ className,
34873
+ ...other
34874
+ } = props;
34875
+ const context2 = React.useContext(ListContext);
34876
+ const ownerState = {
34877
+ ...props,
34878
+ alignItems: context2.alignItems
34879
+ };
34880
+ const classes2 = useUtilityClasses$1d(ownerState);
34881
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(ListItemIconRoot, {
34882
+ className: clsx$1(classes2.root, className),
34883
+ ownerState,
34884
+ ref: ref2,
34885
+ ...other
34886
+ });
34887
+ });
34888
+ ListItemIcon.propTypes = {
34889
+ // ┌────────────────────────────── Warning ──────────────────────────────┐
34890
+ // │ These PropTypes are generated from the TypeScript type definitions. │
34891
+ // │ To update them, edit the d.ts file and run `pnpm proptypes`. │
34892
+ // └─────────────────────────────────────────────────────────────────────┘
34893
+ /**
34894
+ * The content of the component, normally `Icon`, `SvgIcon`,
34895
+ * or a `@mui/icons-material` SVG icon element.
34896
+ */
34897
+ children: PropTypes.node,
34898
+ /**
34899
+ * Override or extend the styles applied to the component.
34900
+ */
34901
+ classes: PropTypes.object,
34902
+ /**
34903
+ * @ignore
34904
+ */
34905
+ className: PropTypes.string,
34906
+ /**
34907
+ * The system prop that allows defining system overrides as well as additional CSS styles.
34908
+ */
34909
+ sx: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), PropTypes.func, PropTypes.object])
34910
+ };
34820
34911
  function getScrollbarSize$1(win2 = window) {
34821
34912
  const documentWidth = win2.document.documentElement.clientWidth;
34822
34913
  return win2.innerWidth - documentWidth;
@@ -35068,7 +35159,7 @@ function getDividerUtilityClass(slot) {
35068
35159
  return generateUtilityClass("MuiDivider", slot);
35069
35160
  }
35070
35161
  const dividerClasses = generateUtilityClasses("MuiDivider", ["root", "absolute", "fullWidth", "inset", "middle", "flexItem", "light", "vertical", "withChildren", "withChildrenVertical", "textAlignRight", "textAlignLeft", "wrapper", "wrapperVertical"]);
35071
- const useUtilityClasses$1d = (ownerState) => {
35162
+ const useUtilityClasses$1c = (ownerState) => {
35072
35163
  const {
35073
35164
  absolute: absolute2,
35074
35165
  children: children3,
@@ -35282,7 +35373,7 @@ const Divider$1 = /* @__PURE__ */ React.forwardRef(function Divider2(inProps, re
35282
35373
  textAlign: textAlign2,
35283
35374
  variant
35284
35375
  };
35285
- const classes2 = useUtilityClasses$1d(ownerState);
35376
+ const classes2 = useUtilityClasses$1c(ownerState);
35286
35377
  return /* @__PURE__ */ jsxRuntimeExports.jsx(DividerRoot, {
35287
35378
  as: component,
35288
35379
  className: clsx$1(classes2.root, className),
@@ -35364,90 +35455,6 @@ Divider$1.propTypes = {
35364
35455
  */
35365
35456
  variant: PropTypes.oneOfType([PropTypes.oneOf(["fullWidth", "inset", "middle"]), PropTypes.string])
35366
35457
  };
35367
- function getListItemIconUtilityClass(slot) {
35368
- return generateUtilityClass("MuiListItemIcon", slot);
35369
- }
35370
- const listItemIconClasses = generateUtilityClasses("MuiListItemIcon", ["root", "alignItemsFlexStart"]);
35371
- const useUtilityClasses$1c = (ownerState) => {
35372
- const {
35373
- alignItems,
35374
- classes: classes2
35375
- } = ownerState;
35376
- const slots = {
35377
- root: ["root", alignItems === "flex-start" && "alignItemsFlexStart"]
35378
- };
35379
- return composeClasses(slots, getListItemIconUtilityClass, classes2);
35380
- };
35381
- const ListItemIconRoot = styled("div", {
35382
- name: "MuiListItemIcon",
35383
- slot: "Root",
35384
- overridesResolver: (props, styles2) => {
35385
- const {
35386
- ownerState
35387
- } = props;
35388
- return [styles2.root, ownerState.alignItems === "flex-start" && styles2.alignItemsFlexStart];
35389
- }
35390
- })(memoTheme(({
35391
- theme
35392
- }) => ({
35393
- minWidth: 56,
35394
- color: (theme.vars || theme).palette.action.active,
35395
- flexShrink: 0,
35396
- display: "inline-flex",
35397
- variants: [{
35398
- props: {
35399
- alignItems: "flex-start"
35400
- },
35401
- style: {
35402
- marginTop: 8
35403
- }
35404
- }]
35405
- })));
35406
- const ListItemIcon = /* @__PURE__ */ React.forwardRef(function ListItemIcon2(inProps, ref2) {
35407
- const props = useDefaultProps({
35408
- props: inProps,
35409
- name: "MuiListItemIcon"
35410
- });
35411
- const {
35412
- className,
35413
- ...other
35414
- } = props;
35415
- const context2 = React.useContext(ListContext);
35416
- const ownerState = {
35417
- ...props,
35418
- alignItems: context2.alignItems
35419
- };
35420
- const classes2 = useUtilityClasses$1c(ownerState);
35421
- return /* @__PURE__ */ jsxRuntimeExports.jsx(ListItemIconRoot, {
35422
- className: clsx$1(classes2.root, className),
35423
- ownerState,
35424
- ref: ref2,
35425
- ...other
35426
- });
35427
- });
35428
- ListItemIcon.propTypes = {
35429
- // ┌────────────────────────────── Warning ──────────────────────────────┐
35430
- // │ These PropTypes are generated from the TypeScript type definitions. │
35431
- // │ To update them, edit the d.ts file and run `pnpm proptypes`. │
35432
- // └─────────────────────────────────────────────────────────────────────┘
35433
- /**
35434
- * The content of the component, normally `Icon`, `SvgIcon`,
35435
- * or a `@mui/icons-material` SVG icon element.
35436
- */
35437
- children: PropTypes.node,
35438
- /**
35439
- * Override or extend the styles applied to the component.
35440
- */
35441
- classes: PropTypes.object,
35442
- /**
35443
- * @ignore
35444
- */
35445
- className: PropTypes.string,
35446
- /**
35447
- * The system prop that allows defining system overrides as well as additional CSS styles.
35448
- */
35449
- sx: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), PropTypes.func, PropTypes.object])
35450
- };
35451
35458
  function getMenuItemUtilityClass(slot) {
35452
35459
  return generateUtilityClass("MuiMenuItem", slot);
35453
35460
  }
@@ -171404,14 +171411,14 @@ class RBush {
171404
171411
  search(bbox2) {
171405
171412
  let node2 = this.data;
171406
171413
  const result = [];
171407
- if (!intersects$3(bbox2, node2)) return result;
171414
+ if (!intersects$4(bbox2, node2)) return result;
171408
171415
  const toBBox = this.toBBox;
171409
171416
  const nodesToSearch = [];
171410
171417
  while (node2) {
171411
171418
  for (let i2 = 0; i2 < node2.children.length; i2++) {
171412
171419
  const child = node2.children[i2];
171413
171420
  const childBBox = node2.leaf ? toBBox(child) : child;
171414
- if (intersects$3(bbox2, childBBox)) {
171421
+ if (intersects$4(bbox2, childBBox)) {
171415
171422
  if (node2.leaf) result.push(child);
171416
171423
  else if (contains$3(bbox2, childBBox)) this._all(child, result);
171417
171424
  else nodesToSearch.push(child);
@@ -171423,13 +171430,13 @@ class RBush {
171423
171430
  }
171424
171431
  collides(bbox2) {
171425
171432
  let node2 = this.data;
171426
- if (!intersects$3(bbox2, node2)) return false;
171433
+ if (!intersects$4(bbox2, node2)) return false;
171427
171434
  const nodesToSearch = [];
171428
171435
  while (node2) {
171429
171436
  for (let i2 = 0; i2 < node2.children.length; i2++) {
171430
171437
  const child = node2.children[i2];
171431
171438
  const childBBox = node2.leaf ? this.toBBox(child) : child;
171432
- if (intersects$3(bbox2, childBBox)) {
171439
+ if (intersects$4(bbox2, childBBox)) {
171433
171440
  if (node2.leaf || contains$3(bbox2, childBBox)) return true;
171434
171441
  nodesToSearch.push(child);
171435
171442
  }
@@ -171741,7 +171748,7 @@ function intersectionArea(a2, b2) {
171741
171748
  function contains$3(a2, b2) {
171742
171749
  return a2.minX <= b2.minX && a2.minY <= b2.minY && b2.maxX <= a2.maxX && b2.maxY <= a2.maxY;
171743
171750
  }
171744
- function intersects$3(a2, b2) {
171751
+ function intersects$4(a2, b2) {
171745
171752
  return b2.minX <= a2.maxX && b2.minY <= a2.maxY && b2.maxX >= a2.minX && b2.maxY >= a2.minY;
171746
171753
  }
171747
171754
  function createNode(children3) {
@@ -210603,22 +210610,22 @@ async function getDecoder(fileDirectory) {
210603
210610
  const Decoder = await importFn();
210604
210611
  return new Decoder(fileDirectory);
210605
210612
  }
210606
- addDecoder([void 0, 1], () => import("./raw-B9vaOZf2.js").then((m2) => m2.default));
210607
- addDecoder(5, () => import("./lzw-C6871vN6.js").then((m2) => m2.default));
210613
+ addDecoder([void 0, 1], () => import("./raw-D1HlNbuO.js").then((m2) => m2.default));
210614
+ addDecoder(5, () => import("./lzw-ietT45zT.js").then((m2) => m2.default));
210608
210615
  addDecoder(6, () => {
210609
210616
  throw new Error("old style JPEG compression is not supported.");
210610
210617
  });
210611
- addDecoder(7, () => import("./jpeg-DxJqLfJK.js").then((m2) => m2.default));
210612
- addDecoder([8, 32946], () => import("./deflate-BtXXUoxP.js").then((m2) => m2.default));
210613
- addDecoder(32773, () => import("./packbits-BRu_E5Nj.js").then((m2) => m2.default));
210618
+ addDecoder(7, () => import("./jpeg-CBEb4Wla.js").then((m2) => m2.default));
210619
+ addDecoder([8, 32946], () => import("./deflate-ZIe3Ahsx.js").then((m2) => m2.default));
210620
+ addDecoder(32773, () => import("./packbits-BDNd8psk.js").then((m2) => m2.default));
210614
210621
  addDecoder(
210615
210622
  34887,
210616
- () => import("./lerc-DdcYw8PI.js").then(async (m2) => {
210623
+ () => import("./lerc-Cb0ZS1W3.js").then(async (m2) => {
210617
210624
  await m2.zstd.init();
210618
210625
  return m2;
210619
210626
  }).then((m2) => m2.default)
210620
210627
  );
210621
- addDecoder(50001, () => import("./webimage-C6HswF-A.js").then((m2) => m2.default));
210628
+ addDecoder(50001, () => import("./webimage-DSebXv_d.js").then((m2) => m2.default));
210622
210629
  function copyNewSize(array2, width2, height2, samplesPerPixel = 1) {
210623
210630
  return new (Object.getPrototypeOf(array2)).constructor(width2 * height2 * samplesPerPixel);
210624
210631
  }
@@ -232777,6 +232784,7 @@ function Legend(props) {
232777
232784
  positionRelative = false,
232778
232785
  highContrast = false,
232779
232786
  obsType,
232787
+ isPointsLayer = false,
232780
232788
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
232781
232789
  featureType: _featureType = void 0,
232782
232790
  // Unused but accepted for API compatibility
@@ -232784,6 +232792,9 @@ function Legend(props) {
232784
232792
  considerSelections = true,
232785
232793
  obsColorEncoding,
232786
232794
  featureSelection,
232795
+ featureFilterMode,
232796
+ featureColor,
232797
+ featureIndex,
232787
232798
  featureLabelsMap,
232788
232799
  featureValueColormap,
232789
232800
  featureValueColormapRange,
@@ -232827,15 +232838,94 @@ function Legend(props) {
232827
232838
  debouncedSetRange(rangeValue);
232828
232839
  }
232829
232840
  }, [debouncedSetRange]);
232841
+ const obsLabel = capitalize$3(obsType ?? null);
232830
232842
  const isDarkTheme = theme === "dark";
232831
- const isStaticColor = obsColorEncoding === "spatialChannelColor" || obsColorEncoding === "spatialLayerColor";
232832
- const isSetColor = obsColorEncoding === "cellSetSelection";
232843
+ const isStaticColor = !isPointsLayer && (obsColorEncoding === "spatialChannelColor" || obsColorEncoding === "spatialLayerColor");
232844
+ const isSetColor = !isPointsLayer && obsColorEncoding === "cellSetSelection";
232833
232845
  const layerColor = Array.isArray(spatialLayerColor) && spatialLayerColor.length === 3 ? spatialLayerColor : getDefaultColor(theme ?? "light");
232834
232846
  const channelColor = Array.isArray(spatialChannelColor) && spatialChannelColor.length === 3 ? spatialChannelColor : getDefaultColor(theme ?? "light");
232835
232847
  const staticColor = obsColorEncoding === "spatialChannelColor" ? channelColor : layerColor;
232836
- const visible = visibleProp && (!considerSelections || ["geneSelection", "geneExpression"].includes(obsColorEncoding ?? "") && featureSelection && Array.isArray(featureSelection) && featureSelection.length >= 1 || isSetColor && (obsSetSelection?.length ?? 0) > 0 && (obsSetColor?.length ?? 0) > 0 || isStaticColor);
232848
+ const visible = visibleProp && (!considerSelections || ["geneSelection", "geneExpression"].includes(obsColorEncoding ?? "") && featureSelection && Array.isArray(featureSelection) && featureSelection.length >= 1 || isSetColor && (obsSetSelection?.length ?? 0) > 0 && (obsSetColor?.length ?? 0) > 0 || isStaticColor || isPointsLayer);
232849
+ const pointsLegendElements = [];
232850
+ if (isPointsLayer) {
232851
+ const MAX_NUM_COLORS = 10;
232852
+ const hasFeatureSelection = Array.isArray(featureSelection) && featureSelection.length > 0;
232853
+ const showUnselected = featureFilterMode !== "featureSelection";
232854
+ if (obsColorEncoding === "spatialLayerColor") {
232855
+ if (!hasFeatureSelection) {
232856
+ pointsLegendElements.push({
232857
+ name: obsLabel,
232858
+ color: staticColor
232859
+ });
232860
+ } else {
232861
+ const limitedFeatureSelection = featureSelection.slice(0, MAX_NUM_COLORS);
232862
+ limitedFeatureSelection.forEach((featureName) => {
232863
+ pointsLegendElements.push({
232864
+ name: featureName,
232865
+ color: staticColor
232866
+ });
232867
+ });
232868
+ }
232869
+ } else if (obsColorEncoding === "geneSelection") {
232870
+ if (!hasFeatureSelection) {
232871
+ pointsLegendElements.push({
232872
+ name: obsLabel,
232873
+ color: staticColor
232874
+ });
232875
+ } else {
232876
+ const limitedFeatureSelection = featureSelection.slice(0, MAX_NUM_COLORS);
232877
+ limitedFeatureSelection.forEach((featureName) => {
232878
+ const featureColorMatch = Array.isArray(featureColor) ? featureColor.find((fc) => fc.name === featureName)?.color : null;
232879
+ pointsLegendElements.push({
232880
+ name: featureName,
232881
+ // If no color is specified for this feature, use staticColor.
232882
+ color: featureColorMatch ?? staticColor
232883
+ });
232884
+ });
232885
+ }
232886
+ } else if (obsColorEncoding === "randomByFeature") {
232887
+ if (!hasFeatureSelection) {
232888
+ pointsLegendElements.push({
232889
+ name: obsLabel,
232890
+ // For now, using black and white for this.
232891
+ // (It should not match any color in PALETTE)
232892
+ color: isDarkTheme ? [255, 255, 255] : [0, 0, 0]
232893
+ });
232894
+ } else {
232895
+ const limitedFeatureSelection = featureSelection.slice(0, MAX_NUM_COLORS);
232896
+ limitedFeatureSelection.forEach((featureName) => {
232897
+ const varIndex = (featureIndex ?? []).indexOf(featureName);
232898
+ const featureColorMatch = varIndex >= 0 ? PALETTE[varIndex % PALETTE.length] : null;
232899
+ pointsLegendElements.push({
232900
+ name: featureName,
232901
+ // If no color is specified for this feature, use staticColor.
232902
+ color: featureColorMatch ?? staticColor
232903
+ });
232904
+ });
232905
+ }
232906
+ } else if (obsColorEncoding === "random") {
232907
+ pointsLegendElements.push({
232908
+ name: obsLabel,
232909
+ // For now, using black and white for this.
232910
+ // (It should not match any color in PALETTE)
232911
+ color: isDarkTheme ? [255, 255, 255] : [0, 0, 0]
232912
+ });
232913
+ }
232914
+ if (showUnselected) {
232915
+ pointsLegendElements.push({
232916
+ name: "Unselected",
232917
+ color: getDefaultColor(theme ?? "light")
232918
+ });
232919
+ }
232920
+ }
232837
232921
  const levelZeroNames = useMemo$1(() => Array.from(new Set(obsSetSelection?.map((setPath) => setPath[0]) || [])), [obsSetSelection]);
232838
- const dynamicHeight = isSetColor && obsSetSelection ? levelZeroNames.length * titleHeight + (obsSetSelection?.length ?? 0) * (rectHeight + rectMarginY) : height2 + (!pointsVisible && contoursVisible ? 25 : 0);
232922
+ const dynamicHeight = isPointsLayer ? (
232923
+ // Height logic for points layers.
232924
+ pointsLegendElements.length * (rectHeight + rectMarginY) + titleHeight
232925
+ ) : (
232926
+ // Height logic for non-points layers.
232927
+ isSetColor && obsSetSelection ? levelZeroNames.length * titleHeight + (obsSetSelection?.length ?? 0) * (rectHeight + rectMarginY) : height2 + (!pointsVisible && contoursVisible ? 25 : 0)
232928
+ );
232839
232929
  const availHeight = maxHeight2 !== null ? Math.max(0, maxHeight2 - 4) : Infinity;
232840
232930
  const needsScroll = Number.isFinite(availHeight) && dynamicHeight > availHeight + 1;
232841
232931
  useEffect(() => {
@@ -232847,8 +232937,8 @@ function Legend(props) {
232847
232937
  svg2.selectAll("g").remove();
232848
232938
  svg2.attr("width", width2).attr("height", dynamicHeight);
232849
232939
  const g2 = svg2.append("g").attr("width", width2).attr("height", dynamicHeight);
232850
- const showInteractiveSlider2 = setFeatureValueColormapRange && ["geneSelection", "geneExpression"].includes(obsColorEncoding ?? "") && pointsVisible && featureValueColormap;
232851
- if (!considerSelections || ["geneSelection", "geneExpression"].includes(obsColorEncoding ?? "")) {
232940
+ const showInteractiveSlider2 = !isPointsLayer && setFeatureValueColormapRange && ["geneSelection", "geneExpression"].includes(obsColorEncoding ?? "") && pointsVisible && featureValueColormap;
232941
+ if (!isPointsLayer && (!considerSelections || ["geneSelection", "geneExpression"].includes(obsColorEncoding ?? ""))) {
232852
232942
  const combinedExtent = combineExtents(extent2 ?? null, featureAggregationStrategy ?? null) || [0, 1];
232853
232943
  const [xMin, xMax] = combinedExtent;
232854
232944
  if (featureValueColormap && pointsVisible) {
@@ -232908,10 +232998,10 @@ function Legend(props) {
232908
232998
  });
232909
232999
  }
232910
233000
  }
232911
- if (isStaticColor) {
233001
+ if (!isPointsLayer && isStaticColor) {
232912
233002
  g2.append("rect").attr("x", 0).attr("y", titleHeight).attr("width", width2 - 4).attr("height", rectHeight).attr("fill", `rgb(${staticColor[0]},${staticColor[1]},${staticColor[2]})`);
232913
233003
  }
232914
- if (isSetColor && obsSetSelection && obsSetColor) {
233004
+ if (!isPointsLayer && isSetColor && obsSetSelection && obsSetColor) {
232915
233005
  const obsSetSelectionByLevelZero = {};
232916
233006
  obsSetSelection.forEach((setPath) => {
232917
233007
  const levelZeroName = setPath[0];
@@ -232932,6 +233022,16 @@ function Legend(props) {
232932
233022
  });
232933
233023
  });
232934
233024
  }
233025
+ if (isPointsLayer) {
233026
+ let y2 = 0;
233027
+ g2.append("text").attr("text-anchor", "start").attr("dominant-baseline", "hanging").attr("x", 0).attr("y", y2).text("Points").style("font-size", "9px").style("fill", foregroundColor);
233028
+ y2 += titleHeight;
233029
+ pointsLegendElements.forEach(({ name: name2, color: color2 }) => {
233030
+ g2.append("rect").attr("x", 0).attr("y", y2).attr("width", rectHeight).attr("height", rectHeight).attr("fill", `rgb(${color2[0]},${color2[1]},${color2[2]})`);
233031
+ g2.append("text").attr("text-anchor", "start").attr("dominant-baseline", "hanging").attr("x", rectHeight + rectMarginX).attr("y", y2).text(name2).style("font-size", "9px").style("fill", foregroundColor);
233032
+ y2 += rectHeight + rectMarginY;
233033
+ });
233034
+ }
232935
233035
  const featureSelectionLabelRaw = featureSelection && featureSelection.length >= 1 && !isStaticColor ? featureSelection.map((geneName) => featureLabelsMap?.get(geneName) || featureLabelsMap?.get(cleanFeatureId(geneName)) || geneName) : null;
232936
233036
  let featureSelectionLabelRawStr = "";
232937
233037
  if (featureAggregationStrategy === "first") {
@@ -232958,12 +233058,11 @@ function Legend(props) {
232958
233058
  }
232959
233059
  const combinedMissing = combineMissings(missing ?? null, featureAggregationStrategy ?? null);
232960
233060
  const featureSelectionLabel = combinedMissing ? `${featureSelectionLabelRawStr} (${Math.round(combinedMissing * 100)}% NaN)` : featureSelectionLabelRawStr;
232961
- const obsLabel = capitalize$3(obsType ?? null);
232962
233061
  const featureLabel = considerSelections ? featureSelectionLabel || capitalize$3(featureValueType ?? null) : capitalize$3(featureValueType ?? null);
232963
233062
  const mainLabel = showObsLabel ? obsLabel : featureLabel;
232964
233063
  const subLabel = showObsLabel ? featureLabel : null;
232965
233064
  const hasSubLabel = subLabel !== null;
232966
- if (!isSetColor) {
233065
+ if (!isPointsLayer && !isSetColor) {
232967
233066
  g2.append("text").attr("text-anchor", hasSubLabel ? "start" : "end").attr("dominant-baseline", "hanging").attr("x", hasSubLabel ? 0 : width2 - 4).attr("y", 0).text(mainLabel ?? "").style("font-size", "10px").style("fill", foregroundColor);
232968
233067
  if (hasSubLabel) {
232969
233068
  g2.append("text").attr("text-anchor", "end").attr("dominant-baseline", "hanging").attr("x", width2 - 5).attr("y", titleHeight + rectHeight).text(subLabel ?? "").style("font-size", "9px").style("fill", foregroundColor);
@@ -233002,7 +233101,7 @@ function Legend(props) {
233002
233101
  showObsLabel,
233003
233102
  staticColor
233004
233103
  ]);
233005
- const showInteractiveSlider = setFeatureValueColormapRange && ["geneSelection", "geneExpression"].includes(obsColorEncoding ?? "") && pointsVisible && featureValueColormap;
233104
+ const showInteractiveSlider = !isPointsLayer && setFeatureValueColormapRange && ["geneSelection", "geneExpression"].includes(obsColorEncoding ?? "") && pointsVisible && featureValueColormap;
233006
233105
  const globalExtent = useMemo$1(() => {
233007
233106
  const combined = combineExtents(extent2 ?? null, featureAggregationStrategy ?? null);
233008
233107
  return combined || [0, 1];
@@ -233075,7 +233174,8 @@ function MultiLegend(props) {
233075
233174
  spotMultiFeatureLabels,
233076
233175
  // Points
233077
233176
  pointLayerScopes,
233078
- pointLayerCoordination
233177
+ pointLayerCoordination,
233178
+ pointMultiIndicesData
233079
233179
  } = props;
233080
233180
  const { classes: classes2 } = useStyles$p();
233081
233181
  const reversedSegmentationLayerScopes = useMemo$1(() => [...segmentationLayerScopes || []].reverse(), [segmentationLayerScopes]);
@@ -233086,7 +233186,8 @@ function MultiLegend(props) {
233086
233186
  const layerSetters = pointLayerCoordination?.[1]?.[layerScope];
233087
233187
  if (!layerCoordination)
233088
233188
  return null;
233089
- const { spatialLayerVisible, obsColorEncoding, obsType, featureType, featureValueType, featureSelection, featureValueColormap, featureValueColormapRange, spatialLayerColor, legendVisible } = layerCoordination;
233189
+ const { spatialLayerVisible, obsColorEncoding, obsType, featureType, featureValueType, featureSelection, featureColor, featureFilterMode, featureValueColormap, featureValueColormapRange, spatialLayerColor, legendVisible } = layerCoordination;
233190
+ const pointIndicesData = pointMultiIndicesData?.[layerScope];
233090
233191
  const { setFeatureValueColormapRange } = layerSetters || {};
233091
233192
  const isStaticColor = obsColorEncoding === "spatialLayerColor";
233092
233193
  const height2 = isStaticColor ? 20 : 36;
@@ -233103,6 +233204,10 @@ function MultiLegend(props) {
233103
233204
  obsColorEncoding,
233104
233205
  spatialLayerColor,
233105
233206
  featureSelection,
233207
+ featureFilterMode,
233208
+ featureColor,
233209
+ featureIndex: pointIndicesData?.featureIndex,
233210
+ isPointsLayer: true,
233106
233211
  // featureLabelsMap={featureLabelsMap} // TODO
233107
233212
  featureValueColormap: featureValueColormap || "viridis",
233108
233213
  featureValueColormapRange: featureValueColormapRange || [0, 1],
@@ -253577,6 +253682,7 @@ class Spatial2 extends AbstractSpatialOrScatterplot {
253577
253682
  this.obsSpotsQuadTree = {};
253578
253683
  this.obsPointsData = {};
253579
253684
  this.obsPointsQuadTree = {};
253685
+ this.obsPointsLoadingStatus = {};
253580
253686
  this.imageLayers = [];
253581
253687
  this.obsSegmentationsLayers = [];
253582
253688
  this.obsSpotsLayers = [];
@@ -253757,10 +253863,131 @@ class Spatial2 extends AbstractSpatialOrScatterplot {
253757
253863
  });
253758
253864
  }
253759
253865
  createPointLayer(layerScope, layerCoordination, layerObsPoints) {
253760
- const { theme, delegateHover, targetZ } = this.props;
253761
- const { spatialLayerVisible, spatialLayerOpacity, obsColorEncoding, spatialLayerColor } = layerCoordination;
253762
- const isStaticColor = obsColorEncoding === "spatialLayerColor";
253866
+ const { theme, delegateHover, targetZ, pointMatrixIndices, setTiledPointsLoadingProgress } = this.props;
253867
+ const { spatialLayerVisible, spatialLayerOpacity, obsColorEncoding, spatialLayerColor, featureSelection, featureFilterMode, featureColor } = layerCoordination;
253868
+ const pointFeatureIndex = pointMatrixIndices?.[layerScope]?.featureIndex;
253869
+ let featureIndices = [];
253870
+ if (Array.isArray(featureSelection) && featureSelection.length >= 1) {
253871
+ if (pointFeatureIndex) {
253872
+ featureIndices = featureSelection.map((geneName) => pointFeatureIndex.indexOf(geneName)).filter((i2) => i2 >= 0);
253873
+ }
253874
+ }
253763
253875
  const staticColor = Array.isArray(spatialLayerColor) && spatialLayerColor.length === 3 ? spatialLayerColor : getDefaultColor(theme);
253876
+ const defaultColor = getDefaultColor(theme);
253877
+ let getFillColor = null;
253878
+ const hasFeatureSelection = Array.isArray(featureSelection) && featureSelection.length > 0;
253879
+ const showUnselected = featureFilterMode !== "featureSelection";
253880
+ const hasMultipleFeaturesSelected = Array.isArray(featureIndices) && featureIndices.length > 1;
253881
+ if (obsColorEncoding === "spatialLayerColor") {
253882
+ getFillColor = (object2, { index: index2, data: data2, target: target2 }) => {
253883
+ if (!hasFeatureSelection || featureIndices.includes(data2.src.featureIndices[index2])) {
253884
+ target2[0] = staticColor[0];
253885
+ target2[1] = staticColor[1];
253886
+ target2[2] = staticColor[2];
253887
+ target2[3] = 255;
253888
+ return target2;
253889
+ }
253890
+ if (!showUnselected) {
253891
+ if (hasMultipleFeaturesSelected) {
253892
+ target2[3] = 0;
253893
+ }
253894
+ return target2;
253895
+ }
253896
+ target2[0] = defaultColor[0];
253897
+ target2[1] = defaultColor[1];
253898
+ target2[2] = defaultColor[2];
253899
+ target2[3] = 255;
253900
+ return target2;
253901
+ };
253902
+ } else if (obsColorEncoding === "geneSelection") {
253903
+ getFillColor = (object2, { index: index2, data: data2, target: target2 }) => {
253904
+ if (!hasFeatureSelection) {
253905
+ target2[0] = staticColor[0];
253906
+ target2[1] = staticColor[1];
253907
+ target2[2] = staticColor[2];
253908
+ target2[3] = 255;
253909
+ return target2;
253910
+ }
253911
+ const isSelected = featureIndices.includes(data2.src.featureIndices[index2]);
253912
+ if (isSelected) {
253913
+ const featureName = pointFeatureIndex?.[data2.src.featureIndices[index2]];
253914
+ const featureColorMatch = Array.isArray(featureColor) ? featureColor.find((fc) => fc.name === featureName)?.color : null;
253915
+ if (featureColorMatch) {
253916
+ target2[0] = featureColorMatch[0];
253917
+ target2[1] = featureColorMatch[1];
253918
+ target2[2] = featureColorMatch[2];
253919
+ target2[3] = 255;
253920
+ return target2;
253921
+ }
253922
+ target2[0] = staticColor[0];
253923
+ target2[1] = staticColor[1];
253924
+ target2[2] = staticColor[2];
253925
+ target2[3] = 255;
253926
+ return target2;
253927
+ }
253928
+ if (!showUnselected) {
253929
+ if (hasMultipleFeaturesSelected) {
253930
+ target2[3] = 0;
253931
+ }
253932
+ return target2;
253933
+ }
253934
+ target2[0] = defaultColor[0];
253935
+ target2[1] = defaultColor[1];
253936
+ target2[2] = defaultColor[2];
253937
+ target2[3] = 255;
253938
+ return target2;
253939
+ };
253940
+ } else if (obsColorEncoding === "randomByFeature") {
253941
+ {
253942
+ getFillColor = (object2, { index: index2, data: data2, target: target2 }) => {
253943
+ if (!hasFeatureSelection || featureIndices.includes(data2.src.featureIndices[index2])) {
253944
+ const varIndex = data2.src.featureIndices[index2];
253945
+ const color2 = PALETTE[varIndex % PALETTE.length];
253946
+ target2[0] = color2[0];
253947
+ target2[1] = color2[1];
253948
+ target2[2] = color2[2];
253949
+ target2[3] = 255;
253950
+ return target2;
253951
+ }
253952
+ if (!showUnselected) {
253953
+ if (hasMultipleFeaturesSelected) {
253954
+ target2[3] = 0;
253955
+ }
253956
+ return target2;
253957
+ }
253958
+ target2[0] = defaultColor[0];
253959
+ target2[1] = defaultColor[1];
253960
+ target2[2] = defaultColor[2];
253961
+ target2[3] = 255;
253962
+ return target2;
253963
+ };
253964
+ }
253965
+ } else if (obsColorEncoding === "random") {
253966
+ {
253967
+ getFillColor = (object2, { index: index2, data: data2, target: target2 }) => {
253968
+ if (!hasFeatureSelection || featureIndices.includes(data2.src.featureIndices[index2])) {
253969
+ const color2 = PALETTE[index2 % PALETTE.length];
253970
+ target2[0] = color2[0];
253971
+ target2[1] = color2[1];
253972
+ target2[2] = color2[2];
253973
+ target2[3] = 255;
253974
+ return target2;
253975
+ }
253976
+ if (!showUnselected) {
253977
+ if (hasMultipleFeaturesSelected) {
253978
+ target2[3] = 0;
253979
+ }
253980
+ return target2;
253981
+ }
253982
+ target2[0] = defaultColor[0];
253983
+ target2[1] = defaultColor[1];
253984
+ target2[2] = defaultColor[2];
253985
+ target2[3] = 255;
253986
+ return target2;
253987
+ };
253988
+ }
253989
+ }
253990
+ const isStaticColor = obsColorEncoding === "spatialLayerColor";
253764
253991
  const getMoleculeColor = (object2, { data: data2, index: index2, target: target2 }) => {
253765
253992
  const obsId = data2.src.obsIndex[index2];
253766
253993
  if (data2.src.obsLabelsMap && data2.src.uniqueObsLabels && data2.src.PALETTE) {
@@ -253772,11 +253999,131 @@ class Spatial2 extends AbstractSpatialOrScatterplot {
253772
253999
  }
253773
254000
  return target2;
253774
254001
  };
253775
- const { obsPointsModelMatrix, obsPoints } = this.obsPointsData?.[layerScope]?.src ?? {};
253776
- const hasZ = obsPoints?.shape?.[0] === 3;
254002
+ const { obsPointsModelMatrix, obsPoints, obsPointsTilingType, loadPointsInRect } = this.obsPointsData?.[layerScope]?.src ?? {};
254003
+ obsPoints?.shape?.[0] === 3;
253777
254004
  const modelMatrix2 = obsPointsModelMatrix?.clone();
253778
- if (hasZ && typeof targetZ !== "number") {
253779
- log$b.warn("Spatial: targetZ is not a number, so the point layer will not be filtered by Z.");
254005
+ if (obsPointsTilingType === "tiled") {
254006
+ return new TileLayer({
254007
+ id: `Tiled-${POINT_LAYER_PREFIX}${layerScope}`,
254008
+ coordinateSystem: COORDINATE_SYSTEM.CARTESIAN,
254009
+ // NOTE: picking is not working due to https://github.com/vitessce/vitessce/issues/2039
254010
+ modelMatrix: modelMatrix2,
254011
+ pickable: true,
254012
+ autoHighlight: true,
254013
+ onHover: (info2) => delegateHover(info2, "point", layerScope),
254014
+ opacity: spatialLayerOpacity,
254015
+ visible: spatialLayerVisible,
254016
+ // Since points are tiled but not multi-resolution,
254017
+ // it provides no benefit to request tiles at different zoom levels.
254018
+ // TODO: Should this value be the same for
254019
+ // every dataset, or do we need to
254020
+ // adjust based on the extent/density of the
254021
+ // point data (and/or account for modelMatrix, etc)?
254022
+ maxZoom: -1,
254023
+ minZoom: -1,
254024
+ tileSize: 512,
254025
+ // refinementStrategy: 'no-overlap',
254026
+ getTileData: async (tileInfo) => {
254027
+ const { index: index2, signal, bbox: bbox2, zoom: zoom2 } = tileInfo;
254028
+ const { z: z2, x: x2, y: y2 } = index2;
254029
+ this.obsPointsLoadingStatus = {
254030
+ ...this.obsPointsLoadingStatus,
254031
+ [`${layerScope}-${z2}-${x2}-${y2}`]: "loading"
254032
+ };
254033
+ setTiledPointsLoadingProgress(this.obsPointsLoadingStatus);
254034
+ signal.addEventListener("abort", () => {
254035
+ this.obsPointsLoadingStatus = {
254036
+ ...this.obsPointsLoadingStatus,
254037
+ [`${layerScope}-${z2}-${x2}-${y2}`]: "aborted"
254038
+ };
254039
+ setTiledPointsLoadingProgress(this.obsPointsLoadingStatus);
254040
+ });
254041
+ const pointsInTile = await loadPointsInRect(bbox2, signal);
254042
+ this.obsPointsLoadingStatus = {
254043
+ ...this.obsPointsLoadingStatus,
254044
+ [`${layerScope}-${z2}-${x2}-${y2}`]: "success"
254045
+ };
254046
+ setTiledPointsLoadingProgress(this.obsPointsLoadingStatus);
254047
+ return {
254048
+ src: pointsInTile.data,
254049
+ length: pointsInTile.shape?.[1] || 0
254050
+ };
254051
+ },
254052
+ renderSubLayers: (subLayerProps) => {
254053
+ const { bbox: bbox2, content: tileData } = subLayerProps.tile;
254054
+ const { left: left2, top: top2, right: right2, bottom: bottom2 } = bbox2;
254055
+ const hasFeatureIndicesMinMax = !showUnselected && Array.isArray(featureIndices) && featureIndices.length === 1;
254056
+ const FILTER_PLACEHOLDER = 0;
254057
+ const featureIndicesMinMax = hasFeatureIndicesMinMax ? [featureIndices[0], featureIndices[0]] : [FILTER_PLACEHOLDER, FILTER_PLACEHOLDER];
254058
+ return new ScatterplotLayer(subLayerProps, {
254059
+ bounds: [left2, top2, right2, bottom2],
254060
+ data: tileData,
254061
+ getRadius: 5,
254062
+ radiusMaxPixels: 3,
254063
+ // getPosition: d => d,
254064
+ // getFillColor: [255, 0, 0],
254065
+ getPosition: (object2, { data: data2, index: index2, target: target2 }) => {
254066
+ target2[0] = data2.src.x[index2];
254067
+ target2[1] = data2.src.y[index2];
254068
+ target2[2] = 0;
254069
+ return target2;
254070
+ },
254071
+ getFillColor,
254072
+ // TODO: Is the picking stuff needed here in the Sublayer, or in the parent TileLayer?
254073
+ pickable: true,
254074
+ autoHighlight: true,
254075
+ onHover: (info2) => delegateHover(info2, "point", layerScope),
254076
+ // Use GPU filtering to filter to only the points in the tile bounding box,
254077
+ // since the row groups may contain points from other tiles.
254078
+ // Note: this can be improved using filterCategories,
254079
+ // but it is not available until post-v9 deck.gl/extensions.
254080
+ filterRange: [[left2, right2], [top2, bottom2], featureIndicesMinMax],
254081
+ getFilterValue: hasFeatureIndicesMinMax ? (object2, { data: data2, index: index2 }) => [
254082
+ data2.src.x[index2],
254083
+ data2.src.y[index2],
254084
+ data2.src.featureIndices[index2]
254085
+ ] : (object2, { data: data2, index: index2 }) => [
254086
+ data2.src.x[index2],
254087
+ data2.src.y[index2],
254088
+ FILTER_PLACEHOLDER
254089
+ ],
254090
+ extensions: [
254091
+ new DataFilterExtension({ filterSize: 3 })
254092
+ ],
254093
+ updateTriggers: {
254094
+ getFillColor: [
254095
+ showUnselected,
254096
+ featureColor,
254097
+ obsColorEncoding,
254098
+ spatialLayerColor,
254099
+ featureSelection,
254100
+ hasMultipleFeaturesSelected
254101
+ ],
254102
+ getFilterValue: [hasFeatureIndicesMinMax, showUnselected, featureSelection],
254103
+ filterRange: [hasFeatureIndicesMinMax, showUnselected, featureSelection]
254104
+ }
254105
+ });
254106
+ },
254107
+ updateTriggers: {
254108
+ getTileData: [
254109
+ showUnselected,
254110
+ featureColor,
254111
+ obsColorEncoding,
254112
+ spatialLayerColor,
254113
+ featureSelection,
254114
+ hasMultipleFeaturesSelected
254115
+ ]
254116
+ }
254117
+ /*
254118
+ onTileError: (error) => {
254119
+
254120
+ },
254121
+ onViewportLoad: (loadedTiles) => {
254122
+ // Called when all tiles in the current viewport are loaded.
254123
+ // An array of loaded Tile instances are passed as argument to this function.
254124
+ },
254125
+ */
254126
+ });
253780
254127
  }
253781
254128
  return new ScatterplotLayer({
253782
254129
  id: `${POINT_LAYER_PREFIX}${layerScope}`,
@@ -253803,16 +254150,7 @@ class Spatial2 extends AbstractSpatialOrScatterplot {
253803
254150
  getFillColor: [obsColorEncoding, staticColor],
253804
254151
  getLineColor: [obsColorEncoding, staticColor]
253805
254152
  },
253806
- ...hasZ && typeof targetZ === "number" ? {
253807
- // TODO: support targetT filtering as well.
253808
- // TODO: allow filtering by Z coordinate (rather than slice index)
253809
- // Reference: https://github.com/vitessce/vitessce/issues/2194
253810
- filterRange: [targetZ, targetZ],
253811
- getFilterValue: (object2, { data: data2, index: index2 }) => data2.src.obsPoints.data[2][index2],
253812
- extensions: [
253813
- new DataFilterExtension({ filterSize: 1 })
253814
- ]
253815
- } : {}
254153
+ ...{}
253816
254154
  });
253817
254155
  }
253818
254156
  createSelectionLayer() {
@@ -254352,28 +254690,45 @@ class Spatial2 extends AbstractSpatialOrScatterplot {
254352
254690
  }
254353
254691
  onUpdatePointsData(layerScope) {
254354
254692
  const { obsPoints, pointMultiObsLabels } = this.props;
254355
- const { obsIndex, obsPoints: layerObsPoints, obsPointsModelMatrix } = obsPoints?.[layerScope] || {};
254693
+ const { obsIndex, obsPoints: layerObsPoints, obsPointsModelMatrix, loadPointsInRect, obsPointsTilingType } = obsPoints?.[layerScope] || {};
254356
254694
  const { obsIndex: obsLabelsIndex, obsLabels } = pointMultiObsLabels?.[layerScope] || {};
254357
- if (layerObsPoints) {
254358
- const getCellCoords = makeDefaultGetObsCoords(layerObsPoints);
254359
- this.obsPointsQuadTree[layerScope] = createQuadTree(layerObsPoints, getCellCoords);
254695
+ if (obsPointsTilingType === "tiled") {
254360
254696
  this.obsPointsData[layerScope] = {
254361
254697
  src: {
254362
- obsIndex,
254363
- obsPoints: layerObsPoints,
254698
+ obsPointsTilingType,
254699
+ obsIndex: null,
254700
+ obsPoints: null,
254364
254701
  obsPointsModelMatrix,
254365
254702
  obsLabelsMap: null,
254366
254703
  uniqueObsLabels: null,
254367
- PALETTE: null
254704
+ PALETTE: null,
254705
+ loadPointsInRect
254368
254706
  },
254369
- length: layerObsPoints.shape[1]
254707
+ length: null
254370
254708
  };
254371
- if (obsLabels) {
254372
- const obsLabelsMap = new Map(obsLabelsIndex.map((key2, i2) => [key2, obsLabels[i2]]));
254373
- const uniqueObsLabels = Array.from(new Set(obsLabels));
254374
- this.obsPointsData[layerScope].src.obsLabelsMap = obsLabelsMap;
254375
- this.obsPointsData[layerScope].src.uniqueObsLabels = uniqueObsLabels;
254376
- this.obsPointsData[layerScope].src.PALETTE = PALETTE;
254709
+ } else {
254710
+ if (layerObsPoints) {
254711
+ const getCellCoords = makeDefaultGetObsCoords(layerObsPoints);
254712
+ this.obsPointsQuadTree[layerScope] = createQuadTree(layerObsPoints, getCellCoords);
254713
+ this.obsPointsData[layerScope] = {
254714
+ src: {
254715
+ obsPointsTilingType,
254716
+ obsIndex,
254717
+ obsPoints: layerObsPoints,
254718
+ obsPointsModelMatrix,
254719
+ obsLabelsMap: null,
254720
+ uniqueObsLabels: null,
254721
+ PALETTE: null
254722
+ },
254723
+ length: layerObsPoints.shape[1]
254724
+ };
254725
+ if (obsLabels) {
254726
+ const obsLabelsMap = new Map(obsLabelsIndex.map((key2, i2) => [key2, obsLabels[i2]]));
254727
+ const uniqueObsLabels = Array.from(new Set(obsLabels));
254728
+ this.obsPointsData[layerScope].src.obsLabelsMap = obsLabelsMap;
254729
+ this.obsPointsData[layerScope].src.uniqueObsLabels = uniqueObsLabels;
254730
+ this.obsPointsData[layerScope].src.PALETTE = PALETTE;
254731
+ }
254377
254732
  }
254378
254733
  }
254379
254734
  }
@@ -254582,6 +254937,7 @@ class Spatial2 extends AbstractSpatialOrScatterplot {
254582
254937
  // Data props.
254583
254938
  "obsPoints",
254584
254939
  "pointMultiObsLabels",
254940
+ "pointMatrixIndices",
254585
254941
  // Coordination props.
254586
254942
  "pointLayerScopes",
254587
254943
  "pointLayerCoordination"
@@ -254736,12 +255092,12 @@ class ErrorBoundary extends React__default.Component {
254736
255092
  }
254737
255093
  }
254738
255094
  const LazySpatialThree = React__default.lazy(async () => {
254739
- const { SpatialWrapper: SpatialWrapper2 } = await import("./index-Boz72UfP.js");
255095
+ const { SpatialWrapper: SpatialWrapper2 } = await import("./index-qB8yVLMX.js");
254740
255096
  return { default: SpatialWrapper2 };
254741
255097
  });
254742
255098
  const SpatialThreeAdapter = React__default.forwardRef((props, ref2) => jsxRuntimeExports.jsx("div", { ref: ref2, style: { width: "100%", height: "100%" }, children: jsxRuntimeExports.jsx(ErrorBoundary, { children: jsxRuntimeExports.jsx(Suspense, { fallback: jsxRuntimeExports.jsx("div", { children: "Loading..." }), children: jsxRuntimeExports.jsx(LazySpatialThree, { ...props }) }) }) }));
254743
255099
  const LazySpatialAccelerated = React__default.lazy(async () => {
254744
- const { SpatialWrapper: SpatialWrapper2 } = await import("./index-B6nrHBqV.js");
255100
+ const { SpatialWrapper: SpatialWrapper2 } = await import("./index-CqOEyAUT.js");
254745
255101
  return { default: SpatialWrapper2 };
254746
255102
  });
254747
255103
  const SpatialAcceleratedAdapter = React__default.forwardRef((props, ref2) => jsxRuntimeExports.jsx("div", { ref: ref2, style: { width: "100%", height: "100%" }, children: jsxRuntimeExports.jsx(ErrorBoundary, { children: jsxRuntimeExports.jsx(Suspense, { fallback: jsxRuntimeExports.jsx("div", { children: "Loading..." }), children: jsxRuntimeExports.jsx(LazySpatialAccelerated, { ...props }) }) }) }));
@@ -254856,12 +255212,22 @@ const DEFAULT_VIEW_STATE = {
254856
255212
  const SET_VIEW_STATE_NOOP = () => {
254857
255213
  };
254858
255214
  function getHoverData(hoverInfo, layerType) {
254859
- const { coordinate, sourceLayer: layer, tile } = hoverInfo;
254860
- if (layerType === "segmentation-bitmask" || layerType === "image") {
255215
+ const { coordinate, sourceLayer: layer, tile, index: pointIndex } = hoverInfo;
255216
+ if (layerType === "segmentation-bitmask" || layerType === "image" || layerType === "point") {
254861
255217
  if (coordinate && layer) {
254862
255218
  if (layer.id.startsWith("Tiled") && tile) {
254863
255219
  const { content: content2, bbox: bbox2, index: { z: z2 } } = tile;
254864
255220
  if (content2) {
255221
+ if (layerType === "point" && pointIndex >= 0) {
255222
+ const { src } = content2 || {};
255223
+ const { x: x2, y: y2, featureIndices } = src || {};
255224
+ return {
255225
+ pointIndex,
255226
+ x: x2?.[pointIndex],
255227
+ y: y2?.[pointIndex],
255228
+ featureIndex: featureIndices?.[pointIndex]
255229
+ };
255230
+ }
254865
255231
  const { data: data2, width: width2, height: height2 } = content2;
254866
255232
  const { left: left2, right: right2, top: top2, bottom: bottom2 } = bbox2;
254867
255233
  const bounds2 = [
@@ -255039,6 +255405,8 @@ function SpatialSubscriber(props) {
255039
255405
  CoordinationType$1.SPATIAL_LAYER_VISIBLE,
255040
255406
  CoordinationType$1.SPATIAL_LAYER_OPACITY,
255041
255407
  CoordinationType$1.OBS_COLOR_ENCODING,
255408
+ CoordinationType$1.FEATURE_COLOR,
255409
+ CoordinationType$1.FEATURE_FILTER_MODE,
255042
255410
  CoordinationType$1.FEATURE_SELECTION,
255043
255411
  CoordinationType$1.FEATURE_VALUE_COLORMAP,
255044
255412
  CoordinationType$1.FEATURE_VALUE_COLORMAP_RANGE,
@@ -255049,7 +255417,10 @@ function SpatialSubscriber(props) {
255049
255417
  CoordinationType$1.LEGEND_VISIBLE
255050
255418
  ], coordinationScopes, coordinationScopesBy, CoordinationType$1.POINT_LAYER);
255051
255419
  const [volumeLoadingStatus, setVolumeLoadingStatus] = useState(null);
255052
- const [{ volumeLoadingProgress }, { setVolumeLoadingProgress }] = useAuxiliaryCoordination(["spatialAcceleratedVolumeLoadingProgress"], coordinationScopes);
255420
+ const [{ volumeLoadingProgress, tiledPointsLoadingProgress }, { setVolumeLoadingProgress, setTiledPointsLoadingProgress }] = useAuxiliaryCoordination([
255421
+ "spatialAcceleratedVolumeLoadingProgress",
255422
+ "spatialTiledPointsLoadingProgress"
255423
+ ], coordinationScopes);
255053
255424
  useEffect(() => {
255054
255425
  if (volumeLoadingStatus && volumeLoadingStatus.loadingProgress) {
255055
255426
  setVolumeLoadingProgress(volumeLoadingStatus);
@@ -255066,6 +255437,7 @@ function SpatialSubscriber(props) {
255066
255437
  const height2 = threeFor3d && deckHeight === void 0 ? threeHeight : deckHeight;
255067
255438
  const [obsPointsData, obsPointsDataStatus, obsPointsUrls, obsPointsErrors] = useMultiObsPoints(coordinationScopes, coordinationScopesBy, loaders, dataset, mergeCoordination, uuid2);
255068
255439
  const [pointMultiObsLabelsData, pointMultiObsLabelsDataStatus, pointMultiObsLabelsErrors] = usePointMultiObsLabels(coordinationScopes, coordinationScopesBy, loaders, dataset);
255440
+ const [pointMultiIndicesData, pointMultiIndicesDataStatus, pointMultiIndicesDataErrors] = usePointMultiObsFeatureMatrixIndices(coordinationScopes, coordinationScopesBy, loaders, dataset);
255069
255441
  const [obsSpotsData, obsSpotsDataStatus, obsSpotsUrls, obsSpotsErrors] = useMultiObsSpots(coordinationScopes, coordinationScopesBy, loaders, dataset, mergeCoordination, uuid2);
255070
255442
  const [obsSpotsSetsData, obsSpotsSetsDataStatus, obsSpotSetsUrls, obsSpotsSetsErrors] = useSpotMultiObsSets(coordinationScopes, coordinationScopesBy, loaders, dataset);
255071
255443
  const [obsSpotsFeatureLabelsData, obsSpotsFeatureLabelsDataStatus, obsSpotsFeatureLabelsUrls, obsSpotsFeatureLabelsErrors] = useSpotMultiFeatureLabels(coordinationScopes, coordinationScopesBy, loaders, dataset);
@@ -255099,6 +255471,7 @@ function SpatialSubscriber(props) {
255099
255471
  ...spotMultiFeatureSelectionErrors,
255100
255472
  ...spotMultiIndicesDataErrors,
255101
255473
  ...pointMultiObsLabelsErrors,
255474
+ ...pointMultiIndicesDataErrors,
255102
255475
  ...segmentationMultiFeatureSelectionErrors,
255103
255476
  ...segmentationMultiIndicesDataErrors,
255104
255477
  ...obsSegmentationsLocationsDataErrors
@@ -255118,6 +255491,7 @@ function SpatialSubscriber(props) {
255118
255491
  // Points
255119
255492
  obsPointsDataStatus,
255120
255493
  pointMultiObsLabelsDataStatus,
255494
+ pointMultiIndicesDataStatus,
255121
255495
  // Segmentations
255122
255496
  obsSegmentationsDataStatus,
255123
255497
  obsSegmentationsSetsDataStatus,
@@ -255305,14 +255679,19 @@ function SpatialSubscriber(props) {
255305
255679
  pointLayerScopes?.forEach((pointLayerScope) => {
255306
255680
  const { setObsHighlight } = pointLayerCoordination?.[1]?.[pointLayerScope] || {};
255307
255681
  if (hoverData && layerType === "point" && layerScope === pointLayerScope) {
255308
- const obsI = hoverData;
255309
- const { obsIndex } = obsPointsData?.[pointLayerScope] || {};
255310
- const obsId = obsIndex?.[obsI];
255311
- if (obsIndex && obsId) {
255682
+ if (typeof hoverData === "object" && hoverData?.pointIndex) {
255312
255683
  showAnyTooltip = true;
255313
- setObsHighlight(obsId);
255684
+ setObsHighlight(hoverData.pointIndex);
255314
255685
  } else {
255315
- setObsHighlight(null);
255686
+ const obsI = hoverData;
255687
+ const { obsIndex } = obsPointsData?.[pointLayerScope] || {};
255688
+ const obsId = obsIndex?.[obsI];
255689
+ if (obsIndex && obsId) {
255690
+ showAnyTooltip = true;
255691
+ setObsHighlight(obsId);
255692
+ } else {
255693
+ setObsHighlight(null);
255694
+ }
255316
255695
  }
255317
255696
  } else {
255318
255697
  setObsHighlight(null);
@@ -255354,7 +255733,7 @@ function SpatialSubscriber(props) {
255354
255733
  target: [targetX2, targetY2, targetZ],
255355
255734
  rotationX,
255356
255735
  rotationOrbit
255357
- } : DEFAULT_VIEW_STATE, orbitAxis, spatialAxisFixed, setViewState: isValidViewState ? setViewState : SET_VIEW_STATE_NOOP, originalViewState, spatialRenderingMode, updateViewInfo: setComponentViewInfo, delegateHover, obsPoints: obsPointsData, pointLayerScopes, pointLayerCoordination, pointMultiObsLabels: pointMultiObsLabelsData, obsSpots: obsSpotsData, spotLayerScopes, spotLayerCoordination, obsSpotsSets: obsSpotsSetsData, spotMatrixIndices: spotMultiIndicesData, spotMultiExpressionData: spotMultiExpressionNormDataAggregated || spotMultiExpressionNormData, segmentationLayerScopes, segmentationLayerCoordination, segmentationChannelScopesByLayer, segmentationChannelCoordination, obsSegmentations: obsSegmentationsData, obsSegmentationsLocations: obsSegmentationsLocationsData, obsSegmentationsSets: obsSegmentationsSetsData, segmentationMatrixIndices: segmentationMultiIndicesData, segmentationMultiExpressionData: segmentationMultiExpressionNormDataAggregated || segmentationMultiExpressionNormData, bitmaskValueIsIndex, images: imageData, imageLayerScopes, imageLayerCoordination, imageChannelScopesByLayer, imageChannelCoordination }), !disableTooltip && jsxRuntimeExports.jsx(SpatialTooltipSubscriber, {
255736
+ } : DEFAULT_VIEW_STATE, orbitAxis, spatialAxisFixed, setViewState: isValidViewState ? setViewState : SET_VIEW_STATE_NOOP, originalViewState, spatialRenderingMode, updateViewInfo: setComponentViewInfo, delegateHover, obsPoints: obsPointsData, pointLayerScopes, pointLayerCoordination, pointMultiObsLabels: pointMultiObsLabelsData, pointMatrixIndices: pointMultiIndicesData, obsSpots: obsSpotsData, spotLayerScopes, spotLayerCoordination, obsSpotsSets: obsSpotsSetsData, spotMatrixIndices: spotMultiIndicesData, spotMultiExpressionData: spotMultiExpressionNormDataAggregated || spotMultiExpressionNormData, segmentationLayerScopes, segmentationLayerCoordination, segmentationChannelScopesByLayer, segmentationChannelCoordination, obsSegmentations: obsSegmentationsData, obsSegmentationsLocations: obsSegmentationsLocationsData, obsSegmentationsSets: obsSegmentationsSetsData, segmentationMatrixIndices: segmentationMultiIndicesData, segmentationMultiExpressionData: segmentationMultiExpressionNormDataAggregated || segmentationMultiExpressionNormData, bitmaskValueIsIndex, images: imageData, imageLayerScopes, imageLayerCoordination, imageChannelScopesByLayer, imageChannelCoordination, setTiledPointsLoadingProgress }), !disableTooltip && jsxRuntimeExports.jsx(SpatialTooltipSubscriber, {
255358
255737
  parentUuid: uuid2,
255359
255738
  width: width2,
255360
255739
  height: height2,
@@ -255395,7 +255774,8 @@ function SpatialSubscriber(props) {
255395
255774
  spotMultiFeatureLabels: obsSpotsFeatureLabelsData,
255396
255775
  // Points
255397
255776
  pointLayerScopes,
255398
- pointLayerCoordination
255777
+ pointLayerCoordination,
255778
+ pointMultiIndicesData
255399
255779
  }
255400
255780
  ), jsxRuntimeExports.jsx(
255401
255781
  ChannelNamesLegend,
@@ -264357,17 +264737,35 @@ function SpotLayerController(props) {
264357
264737
  return jsxRuntimeExports.jsx(Grid$1, { className: lcClasses.layerControllerGrid, children: jsxRuntimeExports.jsx(Paper, { elevation: 4, className: lcClasses.layerControllerRoot, children: jsxRuntimeExports.jsxs(Grid$1, { container: true, direction: "row", justifyContent: "space-between", children: [jsxRuntimeExports.jsx(Grid$1, { size: 1, children: jsxRuntimeExports.jsx(Button, { onClick: handleVisibleChange, className: menuClasses.imageLayerVisibleButton, "aria-label": "Toggle spot layer visibility", children: jsxRuntimeExports.jsx(Visibility, {}) }) }), jsxRuntimeExports.jsx(Grid$1, { size: 1, children: jsxRuntimeExports.jsx(ChannelColorPickerMenu, { theme, color: color2, setColor: setColor2, palette, isStaticColor, isColormap, featureValueColormap, visible }) }), jsxRuntimeExports.jsx(Grid$1, { size: 6, children: jsxRuntimeExports.jsx(Typography, { className: menuClasses.imageLayerName, children: label2 }) }), jsxRuntimeExports.jsx(Grid$1, { size: 2, children: jsxRuntimeExports.jsx(Slider, { value: opacity2, min: 0, max: 1, step: 1e-3, onChange: handleOpacityChange, className: menuClasses.imageLayerOpacitySlider, orientation: "horizontal", "aria-label": `Adjust opacity for layer ${label2}` }) }), jsxRuntimeExports.jsx(Grid$1, { size: 1, children: jsxRuntimeExports.jsx(SpotLayerEllipsisMenu, { filled, setFilled, strokeWidth, setStrokeWidth, featureSelection, obsColorEncoding, setObsColorEncoding, featureValueColormap, setFeatureValueColormap, featureValueColormapRange, setFeatureValueColormapRange, tooltipsVisible, setTooltipsVisible, tooltipCrosshairsVisible, setTooltipCrosshairsVisible, legendVisible, setLegendVisible }) }), jsxRuntimeExports.jsx(Grid$1, { size: 1, children: jsxRuntimeExports.jsx(SvgSpots, { className: classes2.layerTypeSpotIcon }) })] }) }) });
264358
264738
  }
264359
264739
  const useStyles$f = makeStyles()(() => ({
264740
+ pointLayerButton: {
264741
+ borderStyle: "dashed",
264742
+ marginTop: "10px",
264743
+ marginBottom: "10px",
264744
+ fontWeight: 400
264745
+ },
264746
+ pointFeatureControllerGrid: {
264747
+ padding: "0",
264748
+ flexWrap: "nowrap"
264749
+ },
264750
+ pointFeatureExpansionButton: {
264751
+ display: "inline-block",
264752
+ margin: 0,
264753
+ padding: 0,
264754
+ minWidth: 0,
264755
+ lineHeight: 1,
264756
+ width: "50%"
264757
+ },
264360
264758
  layerTypePointIcon: {
264361
264759
  height: "100%",
264362
- marginLeft: "1px",
264760
+ paddingLeft: "2px",
264363
264761
  fill: "currentColor",
264364
- fontSize: "20px",
264762
+ fontSize: "14px",
264365
264763
  width: "50%",
264366
- maxWidth: "20px"
264764
+ maxWidth: "16px"
264367
264765
  }
264368
264766
  }));
264369
264767
  function PointLayerEllipsisMenu(props) {
264370
- const { featureSelection, obsColorEncoding, setObsColorEncoding, featureValueColormapRange, setFeatureValueColormapRange, tooltipsVisible, setTooltipsVisible, tooltipCrosshairsVisible, setTooltipCrosshairsVisible, legendVisible, setLegendVisible } = props;
264768
+ const { featureSelection, featureFilterMode, setFeatureFilterMode, obsColorEncoding, setObsColorEncoding, featureValueColormapRange, setFeatureValueColormapRange, tooltipsVisible, setTooltipsVisible, tooltipCrosshairsVisible, setTooltipCrosshairsVisible, legendVisible, setLegendVisible } = props;
264371
264769
  const [open2, setOpen] = useState(false);
264372
264770
  const { classes: selectClasses2 } = useSelectStyles();
264373
264771
  const { classes: menuClasses } = useEllipsisMenuStyles();
@@ -264376,17 +264774,84 @@ function PointLayerEllipsisMenu(props) {
264376
264774
  const tooltipsVisibleId = $bdb11010cef70236$export$f680877a34711e37();
264377
264775
  const crosshairsVisibleId = $bdb11010cef70236$export$f680877a34711e37();
264378
264776
  const legendVisibleId = $bdb11010cef70236$export$f680877a34711e37();
264379
- return jsxRuntimeExports.jsxs(PopperMenu, { open: open2, setOpen, buttonIcon: jsxRuntimeExports.jsx(MoreVertIcon, {}), buttonClassName: menuClasses.imageLayerMenuButton, containerClassName: menuClasses.imageLayerPopperContainer, withPaper: true, "aria-label": "Open point layer options menu", children: [jsxRuntimeExports.jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsxRuntimeExports.jsx("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: quantitativeColormapId, children: "Color Encoding: " }), jsxRuntimeExports.jsxs(NativeSelect, { onChange: (e3) => setObsColorEncoding(e3.target.value), value: obsColorEncoding, inputProps: { id: quantitativeColormapId, "aria-label": "Color encoding selector" }, classes: { root: selectClasses2.selectRoot }, children: [jsxRuntimeExports.jsx("option", { value: "spatialLayerColor", children: "Static Color" }), jsxRuntimeExports.jsx("option", { value: "obsLabels", children: "Label Value" })] })] }), jsxRuntimeExports.jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsxRuntimeExports.jsx("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: colormapRangeId, children: "Colormap Range: " }), jsxRuntimeExports.jsx(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" })] }), jsxRuntimeExports.jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsxRuntimeExports.jsx("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: tooltipsVisibleId, children: "Tooltips Visible: " }), jsxRuntimeExports.jsx(Checkbox$1, { color: "primary", className: menuClasses.menuItemCheckbox, checked: tooltipsVisible, onChange: (e3, v) => setTooltipsVisible(v), slotProps: { input: { id: tooltipsVisibleId, "aria-label": "Toggle tooltip visibility" } } })] }), jsxRuntimeExports.jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsxRuntimeExports.jsx("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: crosshairsVisibleId, children: "Tooltip Crosshairs Visible: " }), jsxRuntimeExports.jsx(Checkbox$1, { color: "primary", className: menuClasses.menuItemCheckbox, checked: tooltipCrosshairsVisible, onChange: (e3, v) => setTooltipCrosshairsVisible(v), slotProps: { input: { id: crosshairsVisibleId, "aria-label": "Toggle tooltip crosshair visibility" } } })] }), jsxRuntimeExports.jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsxRuntimeExports.jsx("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: legendVisibleId, children: "Legend Visible: " }), jsxRuntimeExports.jsx(Checkbox$1, { color: "primary", className: menuClasses.menuItemCheckbox, checked: legendVisible, onChange: (e3, v) => setLegendVisible(v), slotProps: { input: { id: legendVisibleId, "aria-label": "Toggle legend visibility" } } })] })] });
264777
+ const featureFilterModeId = $bdb11010cef70236$export$f680877a34711e37();
264778
+ return jsxRuntimeExports.jsxs(PopperMenu, { open: open2, setOpen, buttonIcon: jsxRuntimeExports.jsx(MoreVertIcon, {}), buttonClassName: menuClasses.imageLayerMenuButton, containerClassName: menuClasses.imageLayerPopperContainer, withPaper: true, "aria-label": "Open point layer options menu", children: [jsxRuntimeExports.jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsxRuntimeExports.jsx("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: quantitativeColormapId, children: "Color Encoding: " }), jsxRuntimeExports.jsxs(NativeSelect, { onChange: (e3) => setObsColorEncoding(e3.target.value), value: obsColorEncoding, inputProps: { id: quantitativeColormapId, "aria-label": "Color encoding selector" }, classes: { root: selectClasses2.selectRoot }, children: [jsxRuntimeExports.jsx("option", { value: "spatialLayerColor", children: "Static Color" }), jsxRuntimeExports.jsx("option", { value: "geneSelection", children: "Feature Color" }), jsxRuntimeExports.jsx("option", { value: "randomByFeature", children: "Random Color per Feature" }), jsxRuntimeExports.jsx("option", { value: "random", children: "Random Color per Point" })] })] }), jsxRuntimeExports.jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsxRuntimeExports.jsx("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: featureFilterModeId, children: "Filter to Feature Selection: " }), jsxRuntimeExports.jsx(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" } } })] }), jsxRuntimeExports.jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsxRuntimeExports.jsx("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: colormapRangeId, children: "Colormap Range: " }), jsxRuntimeExports.jsx(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" })] }), jsxRuntimeExports.jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsxRuntimeExports.jsx("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: tooltipsVisibleId, children: "Tooltips Visible: " }), jsxRuntimeExports.jsx(Checkbox$1, { color: "primary", className: menuClasses.menuItemCheckbox, checked: tooltipsVisible, onChange: (e3, v) => setTooltipsVisible(v), slotProps: { input: { id: tooltipsVisibleId, "aria-label": "Toggle tooltip visibility" } } })] }), jsxRuntimeExports.jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsxRuntimeExports.jsx("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: crosshairsVisibleId, children: "Tooltip Crosshairs Visible: " }), jsxRuntimeExports.jsx(Checkbox$1, { color: "primary", className: menuClasses.menuItemCheckbox, checked: tooltipCrosshairsVisible, onChange: (e3, v) => setTooltipCrosshairsVisible(v), slotProps: { input: { id: crosshairsVisibleId, "aria-label": "Toggle tooltip crosshair visibility" } } })] }), jsxRuntimeExports.jsxs(MenuItem, { dense: true, disableGutters: true, children: [jsxRuntimeExports.jsx("label", { className: menuClasses.imageLayerMenuLabel, htmlFor: legendVisibleId, children: "Legend Visible: " }), jsxRuntimeExports.jsx(Checkbox$1, { color: "primary", className: menuClasses.menuItemCheckbox, checked: legendVisible, onChange: (e3, v) => setLegendVisible(v), slotProps: { input: { id: legendVisibleId, "aria-label": "Toggle legend visibility" } } })] })] });
264380
264779
  }
264381
264780
  function PointLayerController(props) {
264382
- const { theme, layerScope, layerCoordination, setLayerCoordination, palette = null } = props;
264383
- const { obsType, spatialLayerVisible: visible, spatialLayerOpacity: opacity2, obsColorEncoding, featureSelection, featureValueColormap, featureValueColormapRange, spatialLayerColor: color2, tooltipsVisible, tooltipCrosshairsVisible, legendVisible } = layerCoordination;
264384
- const { setSpatialLayerVisible: setVisible, setSpatialLayerOpacity: setOpacity2, setObsColorEncoding, setFeatureSelection, setFeatureValueColormap, setFeatureValueColormapRange, setSpatialLayerColor: setColor2, setTooltipsVisible, setTooltipCrosshairsVisible, setLegendVisible } = setLayerCoordination;
264781
+ const { theme, layerScope, layerCoordination, setLayerCoordination, palette = null, pointMatrixIndicesData, tiledPointsLoadingProgress } = props;
264782
+ const [open2, setOpen] = useState(false);
264783
+ const loadingDoneFraction = useMemo$1(() => {
264784
+ if (tiledPointsLoadingProgress && typeof tiledPointsLoadingProgress === "object") {
264785
+ return 1 - Object.values(tiledPointsLoadingProgress).filter((status) => status === "loading").length / Object.values(tiledPointsLoadingProgress).length;
264786
+ }
264787
+ return 1;
264788
+ }, [tiledPointsLoadingProgress]);
264789
+ const { obsType, spatialLayerVisible: visible, spatialLayerOpacity: opacity2, obsColorEncoding, featureColor, featureFilterMode, featureSelection, featureValueColormap, featureValueColormapRange, spatialLayerColor, tooltipsVisible, tooltipCrosshairsVisible, legendVisible } = layerCoordination;
264790
+ const { setSpatialLayerVisible: setVisible, setSpatialLayerOpacity: setOpacity2, setObsColorEncoding, setFeatureColor, setFeatureFilterMode, setFeatureSelection, setFeatureValueColormap, setFeatureValueColormapRange, setSpatialLayerColor, setTooltipsVisible, setTooltipCrosshairsVisible, setLegendVisible } = setLayerCoordination;
264385
264791
  const label2 = capitalize$3(obsType);
264386
264792
  const visibleSetting = typeof visible === "boolean" ? visible : true;
264387
264793
  const Visibility = useMemo$1(() => visibleSetting ? VisibilityIcon : VisibilityOffIcon, [visibleSetting]);
264388
- const isStaticColor = obsColorEncoding === "spatialLayerColor";
264389
- const isColormap = obsColorEncoding === "geneSelection";
264794
+ const hasUnspecifiedFeatureColors = useMemo$1(() => {
264795
+ if (Array.isArray(featureSelection)) {
264796
+ if (Array.isArray(featureColor)) {
264797
+ return featureSelection.some((featureName) => {
264798
+ const colorForFeature = featureColor.find((fc) => fc.name === featureName);
264799
+ return !colorForFeature;
264800
+ });
264801
+ }
264802
+ return featureSelection.length > 0;
264803
+ }
264804
+ return true;
264805
+ }, [featureColor, featureSelection]);
264806
+ const isStaticColor = obsColorEncoding === "spatialLayerColor" || obsColorEncoding === "geneSelection";
264807
+ const showStaticColor = obsColorEncoding === "spatialLayerColor" || obsColorEncoding === "geneSelection" && hasUnspecifiedFeatureColors;
264808
+ const isColormap = false;
264809
+ const hasSingleSelectedFeature = obsColorEncoding === "geneSelection" && Array.isArray(featureSelection) && featureSelection.length === 1;
264810
+ const color2 = useMemo$1(() => {
264811
+ if (showStaticColor) {
264812
+ return spatialLayerColor;
264813
+ }
264814
+ if (hasSingleSelectedFeature) {
264815
+ const selectedFeatureColor = featureColor?.find((fc) => fc.name === featureSelection[0])?.color;
264816
+ if (selectedFeatureColor) {
264817
+ return selectedFeatureColor;
264818
+ }
264819
+ }
264820
+ return null;
264821
+ }, [
264822
+ hasSingleSelectedFeature,
264823
+ spatialLayerColor,
264824
+ featureColor,
264825
+ featureSelection,
264826
+ showStaticColor
264827
+ ]);
264828
+ const setColor2 = useCallback((newColor) => {
264829
+ if (showStaticColor) {
264830
+ setSpatialLayerColor(newColor);
264831
+ } else if (hasSingleSelectedFeature) {
264832
+ const featureColorIndex = featureColor?.findIndex((fc) => fc.name === featureSelection[0]);
264833
+ if (featureColorIndex !== void 0 && featureColorIndex >= 0) {
264834
+ const newFeatureColor = [...featureColor];
264835
+ newFeatureColor[featureColorIndex] = {
264836
+ name: featureSelection[0],
264837
+ color: newColor
264838
+ };
264839
+ setFeatureColor(newFeatureColor);
264840
+ } else {
264841
+ setFeatureColor([
264842
+ ...featureColor,
264843
+ { name: featureSelection[0], color: newColor }
264844
+ ]);
264845
+ }
264846
+ }
264847
+ }, [
264848
+ hasSingleSelectedFeature,
264849
+ setSpatialLayerColor,
264850
+ featureColor,
264851
+ setFeatureColor,
264852
+ featureSelection,
264853
+ showStaticColor
264854
+ ]);
264390
264855
  const { classes: classes2 } = useStyles$f();
264391
264856
  const { classes: lcClasses } = useControllerSectionStyles();
264392
264857
  const { classes: menuClasses } = useEllipsisMenuStyles();
@@ -264395,7 +264860,10 @@ function PointLayerController(props) {
264395
264860
  setVisible(nextVisible);
264396
264861
  }, [visible, setVisible]);
264397
264862
  const handleOpacityChange = useCallback((e3, v) => setOpacity2(v), [setOpacity2]);
264398
- return jsxRuntimeExports.jsx(Grid$1, { className: lcClasses.layerControllerGrid, children: jsxRuntimeExports.jsx(Paper, { elevation: 4, className: lcClasses.layerControllerRoot, children: jsxRuntimeExports.jsxs(Grid$1, { container: true, direction: "row", justifyContent: "space-between", children: [jsxRuntimeExports.jsx(Grid$1, { size: 1, children: jsxRuntimeExports.jsx(Button, { onClick: handleVisibleChange, className: menuClasses.imageLayerVisibleButton, "aria-label": "Toggle layer visibility", children: jsxRuntimeExports.jsx(Visibility, {}) }) }), jsxRuntimeExports.jsx(Grid$1, { size: 1, children: jsxRuntimeExports.jsx(ChannelColorPickerMenu, { theme, color: color2, setColor: setColor2, palette, isStaticColor, isColormap, featureValueColormap, visible }) }), jsxRuntimeExports.jsx(Grid$1, { size: 6, children: jsxRuntimeExports.jsx(Typography, { className: menuClasses.imageLayerName, children: label2 }) }), jsxRuntimeExports.jsx(Grid$1, { size: 2, children: jsxRuntimeExports.jsx(Slider, { value: opacity2, min: 0, max: 1, step: 1e-3, onChange: handleOpacityChange, className: menuClasses.imageLayerOpacitySlider, orientation: "horizontal", "aria-label": `Adjust opacity for layer ${label2}` }) }), jsxRuntimeExports.jsx(Grid$1, { size: 1, children: jsxRuntimeExports.jsx(PointLayerEllipsisMenu, { featureSelection, obsColorEncoding, setObsColorEncoding, featureValueColormapRange, setFeatureValueColormapRange, tooltipsVisible, setTooltipsVisible, tooltipCrosshairsVisible, setTooltipCrosshairsVisible, legendVisible, setLegendVisible }) }), jsxRuntimeExports.jsx(Grid$1, { size: 1, children: jsxRuntimeExports.jsx(SvgPoints, { className: classes2.layerTypePointIcon }) })] }) }) });
264863
+ useCallback(() => setOpen((prev2) => !prev2), []);
264864
+ const [coloringTabIndex, setColoringTabIndex] = useState(0);
264865
+ const { featureIndex } = pointMatrixIndicesData || {};
264866
+ return jsxRuntimeExports.jsx(Grid$1, { className: lcClasses.layerControllerGrid, children: jsxRuntimeExports.jsxs(Paper, { elevation: 4, className: lcClasses.layerControllerRoot, children: [jsxRuntimeExports.jsxs(Grid$1, { container: true, direction: "row", justifyContent: "space-between", children: [jsxRuntimeExports.jsx(Grid$1, { size: 1, children: jsxRuntimeExports.jsx(Button, { onClick: handleVisibleChange, className: menuClasses.imageLayerVisibleButton, "aria-label": "Toggle layer visibility", children: jsxRuntimeExports.jsx(Visibility, {}) }) }), jsxRuntimeExports.jsx(Grid$1, { size: 1, children: jsxRuntimeExports.jsx(ChannelColorPickerMenu, { theme, color: color2, setColor: setColor2, palette, isStaticColor, isColormap, featureValueColormap, visible }) }), jsxRuntimeExports.jsx(Grid$1, { size: 6, children: jsxRuntimeExports.jsx(Typography, { className: menuClasses.imageLayerName, children: label2 }) }), jsxRuntimeExports.jsx(Grid$1, { size: 2, children: jsxRuntimeExports.jsx(Slider, { value: opacity2, min: 0, max: 1, step: 1e-3, onChange: handleOpacityChange, className: menuClasses.imageLayerOpacitySlider, orientation: "horizontal", "aria-label": `Adjust opacity for layer ${label2}` }) }), jsxRuntimeExports.jsx(Grid$1, { size: 1, children: jsxRuntimeExports.jsx(PointLayerEllipsisMenu, { featureSelection, obsColorEncoding, setObsColorEncoding, featureValueColormapRange, setFeatureValueColormapRange, tooltipsVisible, setTooltipsVisible, tooltipCrosshairsVisible, setTooltipCrosshairsVisible, legendVisible, setLegendVisible, featureFilterMode, setFeatureFilterMode }) }), jsxRuntimeExports.jsxs(Grid$1, { size: 1, container: true, direction: "row", children: [jsxRuntimeExports.jsx(SvgPoints, { className: classes2.layerTypePointIcon }), null] })] }), loadingDoneFraction < 1 ? jsxRuntimeExports.jsx(Grid$1, { size: 12, container: true, direction: "column", justifyContent: "space-between", className: classes2.pointFeatureControllerGrid, children: jsxRuntimeExports.jsx(LinearProgress, { variant: loadingDoneFraction === 0 ? "indeterminate" : "determinate", value: loadingDoneFraction * 100 }) }) : null, null] }) });
264399
264867
  }
264400
264868
  const useStyles$e = makeStyles()(() => ({
264401
264869
  layerTypeSegmentationIcon: {
@@ -267403,7 +267871,7 @@ function ImageLayerEllipsisMenu(props) {
267403
267871
  const { classes: selectClasses2 } = useSelectStyles();
267404
267872
  const { classes: menuClasses } = useEllipsisMenuStyles();
267405
267873
  const [localLodSliderValue, setLocalLodSliderValue] = useState(convertLodFactorToLogarithmicSliderValue(spatialLodFactor || 1));
267406
- React__default.useEffect(() => {
267874
+ useEffect(() => {
267407
267875
  setLocalLodSliderValue(convertLodFactorToLogarithmicSliderValue(spatialLodFactor || 1));
267408
267876
  }, [spatialLodFactor]);
267409
267877
  const is3dMode = spatialRenderingMode === "3D";
@@ -267539,7 +268007,7 @@ function GlobalDimensionSlider(props) {
267539
268007
  return jsxRuntimeExports.jsx(Grid$1, { className: lcClasses.layerControllerGrid, children: jsxRuntimeExports.jsx(Paper, { elevation: 4, className: lcClasses.layerControllerRoot, children: jsxRuntimeExports.jsxs(Grid$1, { container: true, direction: "row", justifyContent: "space-between", children: [jsxRuntimeExports.jsx(Grid$1, { size: 1, children: jsxRuntimeExports.jsx(SvgDimensions, { className: classes2.dimensionsIcon }) }), jsxRuntimeExports.jsx(Grid$1, { size: 1, children: jsxRuntimeExports.jsx(Typography, { className: classes2.dimensionLabel, children: label2 }) }), jsxRuntimeExports.jsx(Grid$1, { size: 8, children: jsxRuntimeExports.jsx(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` }) }), jsxRuntimeExports.jsx(Grid$1, { size: 2, children: isForZ ? jsxRuntimeExports.jsx(FormGroup, { row: true, className: classes2.switchFormGroup, children: jsxRuntimeExports.jsx(FormControlLabel$1, { control: jsxRuntimeExports.jsx(Switch, { color: "primary", checked: spatialRenderingMode === "3D", onChange: handleRenderingModeChange, name: "is3dMode" }), label: "3D" }) }) : null })] }) }) });
267540
268008
  }
267541
268009
  function LayerController(props) {
267542
- const { theme, coordinationScopesRaw, segmentationLayerScopes, segmentationLayerCoordination, segmentationChannelScopesByLayer, segmentationChannelCoordination, images, imageLayerScopes, imageLayerCoordination, targetT, targetZ, setTargetT, setTargetZ, spatialRenderingMode, setSpatialRenderingMode, imageChannelScopesByLayer, imageChannelCoordination, spotLayerScopes, spotLayerCoordination, pointLayerScopes, pointLayerCoordination, volumeLoadingStatus } = props;
268010
+ 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 } = props;
267543
268011
  const anyLayerHasT = Object.values(images || {}).some((image2) => image2?.image?.instance.hasTStack());
267544
268012
  const anyLayerHasZ = Object.values(images || {}).some((image2) => image2?.image?.instance.hasZStack());
267545
268013
  const maxT = Object.values(images || {}).reduce((a2, h2) => Math.max(a2, h2?.image?.instance.getNumT()), 1) - 1;
@@ -267548,7 +268016,7 @@ function LayerController(props) {
267548
268016
  const reversedSegmentationLayerScopes = useMemo$1(() => [...segmentationLayerScopes || []].reverse(), [segmentationLayerScopes]);
267549
268017
  const reversedSpotLayerScopes = useMemo$1(() => [...spotLayerScopes || []].reverse(), [spotLayerScopes]);
267550
268018
  const reversedPointLayerScopes = useMemo$1(() => [...pointLayerScopes || []].reverse(), [pointLayerScopes]);
267551
- return jsxRuntimeExports.jsxs("div", { children: [anyLayerHasZ ? jsxRuntimeExports.jsx(GlobalDimensionSlider, { label: "Z", targetValue: targetZ, setTargetValue: setTargetZ, max: maxZ, spatialRenderingMode, setSpatialRenderingMode }) : null, anyLayerHasT ? jsxRuntimeExports.jsx(GlobalDimensionSlider, { label: "T", targetValue: targetT, setTargetValue: setTargetT, max: maxT }) : null, pointLayerScopes && reversedPointLayerScopes.map((layerScope) => jsxRuntimeExports.jsx(PointLayerController, { theme, layerScope, layerCoordination: pointLayerCoordination[0][layerScope], setLayerCoordination: pointLayerCoordination[1][layerScope] }, layerScope)), spotLayerScopes && reversedSpotLayerScopes.map((layerScope) => jsxRuntimeExports.jsx(SpotLayerController, { theme, layerScope, layerCoordination: spotLayerCoordination[0][layerScope], setLayerCoordination: spotLayerCoordination[1][layerScope] }, layerScope)), segmentationLayerScopes && reversedSegmentationLayerScopes.map((layerScope) => jsxRuntimeExports.jsx(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) => jsxRuntimeExports.jsx(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))] });
268019
+ return jsxRuntimeExports.jsxs("div", { children: [anyLayerHasZ ? jsxRuntimeExports.jsx(GlobalDimensionSlider, { label: "Z", targetValue: targetZ, setTargetValue: setTargetZ, max: maxZ, spatialRenderingMode, setSpatialRenderingMode }) : null, anyLayerHasT ? jsxRuntimeExports.jsx(GlobalDimensionSlider, { label: "T", targetValue: targetT, setTargetValue: setTargetT, max: maxT }) : null, pointLayerScopes && reversedPointLayerScopes.map((layerScope) => jsxRuntimeExports.jsx(PointLayerController, { theme, layerScope, layerCoordination: pointLayerCoordination[0][layerScope], setLayerCoordination: pointLayerCoordination[1][layerScope], pointMatrixIndicesData: pointMultiIndicesData?.[layerScope], tiledPointsLoadingProgress }, layerScope)), spotLayerScopes && reversedSpotLayerScopes.map((layerScope) => jsxRuntimeExports.jsx(SpotLayerController, { theme, layerScope, layerCoordination: spotLayerCoordination[0][layerScope], setLayerCoordination: spotLayerCoordination[1][layerScope] }, layerScope)), segmentationLayerScopes && reversedSegmentationLayerScopes.map((layerScope) => jsxRuntimeExports.jsx(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) => jsxRuntimeExports.jsx(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))] });
267552
268020
  }
267553
268021
  function LayerControllerSubscriber(props) {
267554
268022
  const { coordinationScopes: coordinationScopesRaw, coordinationScopesBy: coordinationScopesByRaw, closeButtonVisible, downloadButtonVisible, removeGridComponent, theme, title: title2 = "Spatial Layers", uuid: uuid2 } = props;
@@ -267632,6 +268100,8 @@ function LayerControllerSubscriber(props) {
267632
268100
  CoordinationType$1.SPATIAL_LAYER_OPACITY,
267633
268101
  CoordinationType$1.SPATIAL_SPOT_RADIUS,
267634
268102
  CoordinationType$1.OBS_COLOR_ENCODING,
268103
+ CoordinationType$1.FEATURE_COLOR,
268104
+ CoordinationType$1.FEATURE_FILTER_MODE,
267635
268105
  CoordinationType$1.FEATURE_SELECTION,
267636
268106
  CoordinationType$1.FEATURE_VALUE_COLORMAP,
267637
268107
  CoordinationType$1.FEATURE_VALUE_COLORMAP_RANGE,
@@ -267640,7 +268110,10 @@ function LayerControllerSubscriber(props) {
267640
268110
  CoordinationType$1.TOOLTIP_CROSSHAIRS_VISIBLE,
267641
268111
  CoordinationType$1.LEGEND_VISIBLE
267642
268112
  ], coordinationScopes, coordinationScopesBy, CoordinationType$1.POINT_LAYER);
267643
- const [{ volumeLoadingProgress: volumeLoadingStatus }] = useAuxiliaryCoordination(["spatialAcceleratedVolumeLoadingProgress"], coordinationScopes);
268113
+ const [{ volumeLoadingProgress: volumeLoadingStatus, tiledPointsLoadingProgress }] = useAuxiliaryCoordination([
268114
+ "spatialAcceleratedVolumeLoadingProgress",
268115
+ "spatialTiledPointsLoadingProgress"
268116
+ ], coordinationScopes);
267644
268117
  const [spatialLayout] = useComponentLayout("spatial", ["spatialImageLayer"], coordinationScopes);
267645
268118
  const layerControllerRef = useRef();
267646
268119
  const [componentWidth, componentHeight] = useClosestVitessceContainerSize(layerControllerRef);
@@ -267649,19 +268122,22 @@ function LayerControllerSubscriber(props) {
267649
268122
  const [imageData, imageDataStatus, imageUrls, imageErrors] = useMultiImages(coordinationScopes, coordinationScopesBy, loaders, dataset, mergeCoordination, uuid2);
267650
268123
  const [obsSpotsData, obsSpotsDataStatus, obsSpotsUrls, obsSpotsErrors] = useMultiObsSpots(coordinationScopes, coordinationScopesBy, loaders, dataset, mergeCoordination, uuid2);
267651
268124
  const [obsPointsData, obsPointsDataStatus, obsPointsUrls, obsPointsErrors] = useMultiObsPoints(coordinationScopes, coordinationScopesBy, loaders, dataset, mergeCoordination, uuid2);
268125
+ const [pointMultiIndicesData, pointMultiIndicesDataStatus, pointMultiIndicesDataErrors] = usePointMultiObsFeatureMatrixIndices(coordinationScopes, coordinationScopesBy, loaders, dataset);
267652
268126
  const errors = [
267653
268127
  ...obsSegmentationsErrors,
267654
268128
  ...imageErrors,
267655
268129
  ...obsSpotsErrors,
267656
- ...obsPointsErrors
268130
+ ...obsPointsErrors,
268131
+ ...pointMultiIndicesDataErrors
267657
268132
  ];
267658
268133
  const isReady = useReady([
267659
268134
  obsSpotsDataStatus,
267660
268135
  obsPointsDataStatus,
267661
268136
  obsSegmentationsDataStatus,
267662
- imageDataStatus
268137
+ imageDataStatus,
268138
+ pointMultiIndicesDataStatus
267663
268139
  ]);
267664
- return jsxRuntimeExports.jsx(TitleInfo, { title: title2, isScroll: true, closeButtonVisible, downloadButtonVisible, removeGridComponent, theme, isReady, errors, children: jsxRuntimeExports.jsx(LayerController, { theme, coordinationScopesRaw, segmentationLayerScopes, segmentationLayerCoordination, segmentationChannelScopesByLayer, segmentationChannelCoordination, images: imageData, imageLayerScopes, imageLayerCoordination, targetT, targetZ, setTargetT, setTargetZ, spatialRenderingMode, setSpatialRenderingMode, imageChannelScopesByLayer, imageChannelCoordination, spotLayerScopes, spotLayerCoordination, pointLayerScopes, pointLayerCoordination, volumeLoadingStatus }) });
268140
+ return jsxRuntimeExports.jsx(TitleInfo, { title: title2, isScroll: true, closeButtonVisible, downloadButtonVisible, removeGridComponent, theme, isReady, errors, children: jsxRuntimeExports.jsx(LayerController, { theme, coordinationScopesRaw, segmentationLayerScopes, segmentationLayerCoordination, segmentationChannelScopesByLayer, segmentationChannelCoordination, images: imageData, imageLayerScopes, imageLayerCoordination, targetT, targetZ, setTargetT, setTargetZ, spatialRenderingMode, setSpatialRenderingMode, imageChannelScopesByLayer, imageChannelCoordination, spotLayerScopes, spotLayerCoordination, pointLayerScopes, pointLayerCoordination, pointMultiIndicesData, volumeLoadingStatus, tiledPointsLoadingProgress }) });
267665
268141
  }
267666
268142
  const isIterable$2 = (obj) => Symbol.iterator in obj;
267667
268143
  const hasIterableEntries = (value2) => (
@@ -269576,7 +270052,7 @@ function HiglassGlobalStyles(props) {
269576
270052
  }
269577
270053
  register({ dataFetcher: ZarrMultivecDataFetcher_default, config: ZarrMultivecDataFetcher_default.config }, { pluginType: "dataFetcher" });
269578
270054
  const LazyHiGlassComponent = React__default.lazy(async () => {
269579
- const { HiGlassComponent } = await import("./higlass-DVAhGN58.js");
270055
+ const { HiGlassComponent } = await import("./higlass-_elX6XYK.js");
269580
270056
  return { default: HiGlassComponent };
269581
270057
  });
269582
270058
  const HG_SIZE = 800;
@@ -272523,7 +272999,7 @@ function NeuroglancerGlobalStyles(props) {
272523
272999
  const { classes: classes2 } = props;
272524
273000
  return jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx(GlobalStyles$3, { styles: globalNeuroglancerCss }), jsxRuntimeExports.jsx(ScopedGlobalStyles, { styles: globalNeuroglancerStyles, parentClassName: classes2.neuroglancerWrapper })] });
272525
273001
  }
272526
- const LazyReactNeuroglancer = React__default.lazy(() => import("./ReactNeuroglancer-AxraaKy7.js"));
273002
+ const LazyReactNeuroglancer = React__default.lazy(() => import("./ReactNeuroglancer-DDBFlWFo.js"));
272527
273003
  function createWorker() {
272528
273004
  return new WorkerFactory();
272529
273005
  }
@@ -293697,7 +294173,7 @@ function cureLocalIntersections(start2, triangles, dim) {
293697
294173
  let p = start2;
293698
294174
  do {
293699
294175
  const a2 = p.prev, b2 = p.next.next;
293700
- if (!equals$1(a2, b2) && intersects$2(a2, p, p.next, b2) && locallyInside(a2, b2) && locallyInside(b2, a2)) {
294176
+ if (!equals$1(a2, b2) && intersects$3(a2, p, p.next, b2) && locallyInside(a2, b2) && locallyInside(b2, a2)) {
293701
294177
  triangles.push(a2.i / dim | 0);
293702
294178
  triangles.push(p.i / dim | 0);
293703
294179
  triangles.push(b2.i / dim | 0);
@@ -293875,7 +294351,7 @@ function area$3(p, q, r3) {
293875
294351
  function equals$1(p1, p2) {
293876
294352
  return p1.x === p2.x && p1.y === p2.y;
293877
294353
  }
293878
- function intersects$2(p1, q1, p2, q2) {
294354
+ function intersects$3(p1, q1, p2, q2) {
293879
294355
  const o1 = sign$2(area$3(p1, q1, p2));
293880
294356
  const o2 = sign$2(area$3(p1, q1, q2));
293881
294357
  const o3 = sign$2(area$3(p2, q2, p1));
@@ -293896,7 +294372,7 @@ function sign$2(num) {
293896
294372
  function intersectsPolygon(a2, b2) {
293897
294373
  let p = a2;
293898
294374
  do {
293899
- if (p.i !== a2.i && p.next.i !== a2.i && p.i !== b2.i && p.next.i !== b2.i && intersects$2(p, p.next, a2, b2)) return true;
294375
+ if (p.i !== a2.i && p.next.i !== a2.i && p.i !== b2.i && p.next.i !== b2.i && intersects$3(p, p.next, a2, b2)) return true;
293900
294376
  p = p.next;
293901
294377
  } while (p !== a2);
293902
294378
  return false;
@@ -322907,7 +323383,7 @@ function place(b2, a2, c2) {
322907
323383
  c2.y = a2.y;
322908
323384
  }
322909
323385
  }
322910
- function intersects$1(a2, b2) {
323386
+ function intersects$2(a2, b2) {
322911
323387
  var dr = a2.r + b2.r - 1e-6, dx = b2.x - a2.x, dy = b2.y - a2.y;
322912
323388
  return dr > 0 && dr * dr > dx * dx + dy * dy;
322913
323389
  }
@@ -322937,13 +323413,13 @@ function packSiblingsRandom(circles, random2) {
322937
323413
  j = b2.next, k = a2.previous, sj = b2._.r, sk = a2._.r;
322938
323414
  do {
322939
323415
  if (sj <= sk) {
322940
- if (intersects$1(j._, c2._)) {
323416
+ if (intersects$2(j._, c2._)) {
322941
323417
  b2 = j, a2.next = b2, b2.previous = a2, --i2;
322942
323418
  continue pack;
322943
323419
  }
322944
323420
  sj += j._.r, j = j.next;
322945
323421
  } else {
322946
- if (intersects$1(k._, c2._)) {
323422
+ if (intersects$2(k._, c2._)) {
322947
323423
  a2 = k, a2.next = b2, b2.previous = a2, --i2;
322948
323424
  continue pack;
322949
323425
  }
@@ -358529,7 +359005,7 @@ class ObsPointsCsvLoader extends CsvLoader {
358529
359005
  data: [obsLocationsX, obsLocationsY],
358530
359006
  shape: [2, obsLocationsX.length]
358531
359007
  };
358532
- this.cachedResult = { obsIndex, obsPoints };
359008
+ this.cachedResult = { obsIndex, obsPoints, obsPointsTilingType: "full" };
358533
359009
  return this.cachedResult;
358534
359010
  }
358535
359011
  async load() {
@@ -360161,7 +360637,7 @@ class ObsPointsAnndataLoader extends AbstractTwoStepLoader {
360161
360637
  this.dataSource.loadObsIndex(path2),
360162
360638
  this.loadPoints()
360163
360639
  ]);
360164
- return new LoaderResult({ obsIndex, obsPoints }, null, coordinationValues);
360640
+ return new LoaderResult({ obsIndex, obsPoints, obsPointsTilingType: "full" }, null, coordinationValues);
360165
360641
  }
360166
360642
  }
360167
360643
  class ObsLocationsAnndataLoader extends AbstractTwoStepLoader {
@@ -361764,20 +362240,31 @@ class SpatialDataObsPointsLoader extends AbstractTwoStepLoader {
361764
362240
  * @returns {Promise} A promise for an array of columns.
361765
362241
  */
361766
362242
  async loadPoints() {
361767
- const { path: path2 } = this.options;
362243
+ const { path: path2, featureIndexColumn } = this.options;
361768
362244
  if (this.locations) {
361769
362245
  return this.locations;
361770
362246
  }
361771
362247
  let locations;
361772
362248
  const formatVersion = await this.dataSource.getPointsFormatVersion(path2);
361773
362249
  if (formatVersion === "0.1") {
361774
- locations = await this.dataSource.loadPoints(path2);
362250
+ locations = await this.dataSource.loadPoints(path2, featureIndexColumn);
361775
362251
  } else {
361776
362252
  throw new UnknownSpatialDataFormatError("Only points format version 0.1 is supported.");
361777
362253
  }
361778
362254
  this.locations = locations;
361779
362255
  return this.locations;
361780
362256
  }
362257
+ async loadPointsInRect(bounds2, signal) {
362258
+ const { path: path2, featureIndexColumn, mortonCodeColumn } = this.options;
362259
+ let locations;
362260
+ const formatVersion = await this.dataSource.getPointsFormatVersion(path2);
362261
+ if (formatVersion === "0.1") {
362262
+ locations = await this.dataSource.loadPointsInRect(path2, bounds2, signal, featureIndexColumn, mortonCodeColumn);
362263
+ } else {
362264
+ throw new UnknownSpatialDataFormatError("Only points format version 0.1 is supported.");
362265
+ }
362266
+ return locations;
362267
+ }
361781
362268
  async loadObsIndex() {
361782
362269
  const { tablePath, path: path2 } = this.options;
361783
362270
  if (tablePath) {
@@ -361790,11 +362277,28 @@ class SpatialDataObsPointsLoader extends AbstractTwoStepLoader {
361790
362277
  }
361791
362278
  return null;
361792
362279
  }
362280
+ async supportsTiling() {
362281
+ const { path: path2, featureIndexColumn: featureIndexColumnNameFromOptions, mortonCodeColumn } = this.options;
362282
+ const zattrs = await this.dataSource.loadSpatialDataElementAttrs(path2);
362283
+ const { spatialdata_attrs: spatialDataAttrs } = zattrs;
362284
+ const { feature_key: featureKey } = spatialDataAttrs;
362285
+ const featureIndexColumnName = featureIndexColumnNameFromOptions ?? `${featureKey}_codes`;
362286
+ const [formatVersion, hasRequiredColumnsAndRowGroupSize] = await Promise.all([
362287
+ // Check the points format version.
362288
+ this.dataSource.getPointsFormatVersion(path2),
362289
+ // Check the size of parquet row groups,
362290
+ // and for the presence of morton_code_2d and feature_index columns.
362291
+ this.dataSource.supportsLoadPointsInRect(path2, featureIndexColumnName, mortonCodeColumn)
362292
+ ]);
362293
+ const isSupportedVersion = formatVersion === "0.1";
362294
+ const boundingBox = zattrs?.bounding_box;
362295
+ const hasBoundingBox2D = typeof boundingBox?.x_max === "number" && typeof boundingBox?.x_min === "number" && typeof boundingBox?.y_max === "number" && typeof boundingBox?.y_min === "number";
362296
+ return isSupportedVersion && hasBoundingBox2D && hasRequiredColumnsAndRowGroupSize;
362297
+ }
361793
362298
  async load() {
361794
- const [obsIndex, obsPoints, modelMatrix2] = await Promise.all([
361795
- this.loadObsIndex(),
361796
- this.loadPoints(),
361797
- this.loadModelMatrix()
362299
+ const [modelMatrix2, supportsTiling] = await Promise.all([
362300
+ this.loadModelMatrix(),
362301
+ this.supportsTiling()
361798
362302
  ]);
361799
362303
  const coordinationValues = {
361800
362304
  pointLayer: CL({
@@ -361810,7 +362314,36 @@ class SpatialDataObsPointsLoader extends AbstractTwoStepLoader {
361810
362314
  // additionalObsSets: null,
361811
362315
  })
361812
362316
  };
361813
- return new LoaderResult({ obsIndex, obsPoints, obsPointsModelMatrix: modelMatrix2 }, null, coordinationValues);
362317
+ let obsIndex = null;
362318
+ let obsPoints = null;
362319
+ const featureIndices = null;
362320
+ if (!supportsTiling) {
362321
+ [obsIndex, obsPoints] = await Promise.all([
362322
+ this.loadObsIndex(),
362323
+ this.loadPoints()
362324
+ ]);
362325
+ }
362326
+ return new LoaderResult({
362327
+ /* obsIndex: ["1"], // TEMP
362328
+ obsPoints: { // TEMP
362329
+ shape: [2, 1],
362330
+ data: [[0], [0]],
362331
+ }, */
362332
+ // These will be null if tiling is supported.
362333
+ obsIndex,
362334
+ obsPoints,
362335
+ featureIndices,
362336
+ obsPointsModelMatrix: modelMatrix2,
362337
+ // Return 'tiled' if the morton_code_2d column
362338
+ // and bounding_box metadata are present,
362339
+ // and the row group size is small enough.
362340
+ // Otherwise, return 'full'.
362341
+ obsPointsTilingType: supportsTiling ? "tiled" : "full",
362342
+ // TEMPORARY: probably makes more sense to pass the loader instance all the way down.
362343
+ // Caller can then decide whether to use loader.load vs. loader.loadPointsInRect.
362344
+ // May need another function such as loader.supportsPointInRect() true/false.
362345
+ loadPointsInRect: this.loadPointsInRect.bind(this)
362346
+ }, null, coordinationValues);
361814
362347
  }
361815
362348
  }
361816
362349
  class SpatialDataObsSetsLoader extends ObsSetsAnndataLoader {
@@ -371205,13 +371738,470 @@ function tableFromIPC(input) {
371205
371738
  }
371206
371739
  return new Table(reader.readAll());
371207
371740
  }
371741
+ const MORTON_CODE_NUM_BITS = 32;
371742
+ const MORTON_CODE_VALUE_MAX = 2 ** (MORTON_CODE_NUM_BITS / 2) - 1;
371743
+ function origCoordToNormCoord(origCoord, origXMin, origXMax, origYMin, origYMax) {
371744
+ const [origX, origY] = origCoord;
371745
+ const origXRange = origXMax - origXMin;
371746
+ const origYRange = origYMax - origYMin;
371747
+ return [
371748
+ // Clamp to zero at low end, since using unsigned ints.
371749
+ Math.max(Math.floor((origX - origXMin) / origXRange * MORTON_CODE_VALUE_MAX), 0),
371750
+ Math.max(Math.floor((origY - origYMin) / origYRange * MORTON_CODE_VALUE_MAX), 0)
371751
+ ];
371752
+ }
371753
+ function intersects$1(ax0, ay0, ax1, ay1, bx0, by0, bx1, by1) {
371754
+ return !(ax1 < bx0 || bx1 < ax0 || ay1 < by0 || by1 < ay0);
371755
+ }
371756
+ function contained(ix0, iy0, ix1, iy1, ox0, oy0, ox1, oy1) {
371757
+ return ox0 <= ix0 && ix0 <= ix1 && ix1 <= ox1 && (oy0 <= iy0 && iy0 <= iy1 && iy1 <= oy1);
371758
+ }
371759
+ function pointInside(x2, y2, rx0, ry0, rx1, ry1) {
371760
+ return rx0 <= x2 && x2 <= rx1 && (ry0 <= y2 && y2 <= ry1);
371761
+ }
371762
+ function cellRange(prefix2, level, bits) {
371763
+ const shift2 = 2 * (bits - level);
371764
+ const power = 2 ** shift2;
371765
+ const lo = prefix2 * power;
371766
+ const hi = (prefix2 + 1) * power - 1;
371767
+ return [lo, hi];
371768
+ }
371769
+ function mergeAdjacent(intervals2) {
371770
+ if (intervals2.length === 0) {
371771
+ return [];
371772
+ }
371773
+ intervals2.sort((a2, b2) => a2[0] - b2[0]);
371774
+ const merged = [intervals2[0]];
371775
+ for (let i2 = 1; i2 < intervals2.length; i2++) {
371776
+ const [lo, hi] = intervals2[i2];
371777
+ const [mlo, mhi] = merged[merged.length - 1];
371778
+ if (lo <= mhi + 1) {
371779
+ merged[merged.length - 1] = [mlo, Math.max(mhi, hi)];
371780
+ } else {
371781
+ merged.push([lo, hi]);
371782
+ }
371783
+ }
371784
+ return merged;
371785
+ }
371786
+ function zcoverRectangle(rx0, ry0, rx1, ry1, bits, stopLevel = null, merge2 = true) {
371787
+ const maxCoord = (1 << bits) - 1;
371788
+ if (!(rx0 >= 0 && rx0 <= rx1 && rx1 <= maxCoord && ry0 >= 0 && ry0 <= ry1 && ry1 <= maxCoord)) {
371789
+ throw new Error("Rectangle out of bounds for given bits.");
371790
+ }
371791
+ const intervals2 = [];
371792
+ const stack2 = [[0, 0, 0, 0, maxCoord, maxCoord]];
371793
+ while (stack2.length > 0) {
371794
+ const [prefix2, level, xmin, ymin, xmax, ymax] = stack2.pop();
371795
+ if (!intersects$1(xmin, ymin, xmax, ymax, rx0, ry0, rx1, ry1)) {
371796
+ continue;
371797
+ }
371798
+ if (stopLevel !== null && level === stopLevel) {
371799
+ intervals2.push(cellRange(prefix2, level, bits));
371800
+ continue;
371801
+ }
371802
+ if (contained(xmin, ymin, xmax, ymax, rx0, ry0, rx1, ry1)) {
371803
+ intervals2.push(cellRange(prefix2, level, bits));
371804
+ continue;
371805
+ }
371806
+ if (level === bits) {
371807
+ if (pointInside(xmin, ymin, rx0, ry0, rx1, ry1)) {
371808
+ intervals2.push(cellRange(prefix2, level, bits));
371809
+ }
371810
+ continue;
371811
+ }
371812
+ const midx = Math.floor((xmin + xmax) / 2);
371813
+ const midy = Math.floor((ymin + ymax) / 2);
371814
+ stack2.push([prefix2 << 2 | 0, level + 1, xmin, ymin, midx, midy]);
371815
+ stack2.push([prefix2 << 2 | 1, level + 1, midx + 1, ymin, xmax, midy]);
371816
+ stack2.push([prefix2 << 2 | 2, level + 1, xmin, midy + 1, midx, ymax]);
371817
+ stack2.push([prefix2 << 2 | 3, level + 1, midx + 1, midy + 1, xmax, ymax]);
371818
+ }
371819
+ return merge2 ? mergeAdjacent(intervals2) : intervals2;
371820
+ }
371821
+ function sdataMortonQueryRectAux(boundingBox, origRect) {
371822
+ const xMin = boundingBox.x_min;
371823
+ const xMax = boundingBox.x_max;
371824
+ const yMin = boundingBox.y_min;
371825
+ const yMax = boundingBox.y_max;
371826
+ const normRect = [
371827
+ origCoordToNormCoord(origRect[0], xMin, xMax, yMin, yMax),
371828
+ origCoordToNormCoord(origRect[1], xMin, xMax, yMin, yMax)
371829
+ ];
371830
+ const mortonIntervals = zcoverRectangle(normRect[0][0], normRect[0][1], normRect[1][0], normRect[1][1], 16, null, true);
371831
+ return mortonIntervals;
371832
+ }
371208
371833
  async function getParquetModule() {
371209
371834
  const module2 = await import(
371210
371835
  /* webpackIgnore: true */
371211
- "https://unpkg.com/parquet-wasm@0.6.1/esm/parquet_wasm.js"
371836
+ "https://cdn.vitessce.io/parquet-wasm@2c23652/esm/parquet_wasm.js"
371212
371837
  );
371213
371838
  await module2.default();
371214
- return { readParquet: module2.readParquet, readSchema: module2.readSchema };
371839
+ return {
371840
+ readParquet: module2.readParquet,
371841
+ readSchema: module2.readSchema,
371842
+ readMetadata: module2.readMetadata,
371843
+ // Added in fork
371844
+ readParquetRowGroup: module2.readParquetRowGroup
371845
+ // Added in fork
371846
+ };
371847
+ }
371848
+ async function _getParquetModule({ queryClient }) {
371849
+ return queryClient.fetchQuery({
371850
+ queryKey: ["parquetModule"],
371851
+ staleTime: Infinity,
371852
+ queryFn: getParquetModule,
371853
+ meta: { queryClient }
371854
+ });
371855
+ }
371856
+ async function _loadParquetBytes({ queryClient, store }, parquetPath, rangeQuery = void 0, partIndex = void 0) {
371857
+ return queryClient.fetchQuery({
371858
+ queryKey: ["SpatialDataTableSource", "_loadParquetBytes", parquetPath, rangeQuery, partIndex],
371859
+ staleTime: Infinity,
371860
+ queryFn: async (ctx) => {
371861
+ const store2 = ctx.meta?.store;
371862
+ const rangeQuery2 = ctx.queryKey[3];
371863
+ const { offset: offset2, length: length2, suffixLength } = rangeQuery2 || {};
371864
+ let getter2 = (path2) => store2.get(path2);
371865
+ if (rangeQuery2 !== void 0 && store2.getRange) {
371866
+ if (suffixLength !== void 0) {
371867
+ getter2 = (path2) => store2.getRange(path2, {
371868
+ suffixLength
371869
+ });
371870
+ } else {
371871
+ getter2 = (path2) => store2.getRange(path2, {
371872
+ offset: offset2,
371873
+ length: length2
371874
+ });
371875
+ }
371876
+ }
371877
+ let parquetBytes;
371878
+ if (partIndex === void 0) {
371879
+ parquetBytes = await getter2(`/${parquetPath}`);
371880
+ }
371881
+ if (!parquetBytes) {
371882
+ const part0Path = `${parquetPath}/part.${partIndex ?? 0}.parquet`;
371883
+ parquetBytes = await getter2(`/${part0Path}`);
371884
+ }
371885
+ return parquetBytes;
371886
+ },
371887
+ meta: { store }
371888
+ });
371889
+ }
371890
+ async function _loadParquetSchemaBytes({ queryClient, store }, parquetPath, partIndex = void 0) {
371891
+ return queryClient.fetchQuery({
371892
+ queryKey: ["SpatialDataTableSource", "_loadParquetSchemaBytes", parquetPath, partIndex],
371893
+ staleTime: Infinity,
371894
+ queryFn: async (ctx) => {
371895
+ const store2 = ctx.meta?.store;
371896
+ if (store2.getRange) {
371897
+ const TAIL_LENGTH = 8;
371898
+ let partZeroPath = parquetPath;
371899
+ let tailBytes = await store2.getRange(`/${partZeroPath}`, {
371900
+ suffixLength: TAIL_LENGTH
371901
+ });
371902
+ if (!tailBytes) {
371903
+ partZeroPath = `${parquetPath}/part.${partIndex ?? 0}.parquet`;
371904
+ tailBytes = await store2.getRange(`/${partZeroPath}`, {
371905
+ suffixLength: TAIL_LENGTH
371906
+ });
371907
+ }
371908
+ if (!tailBytes || tailBytes.length < TAIL_LENGTH) {
371909
+ throw new Error(`Failed to load parquet footerLength for ${partZeroPath}`);
371910
+ }
371911
+ const footerLength = new DataView(tailBytes.buffer).getInt32(0, true);
371912
+ const magic = new TextDecoder().decode(tailBytes.slice(4, 8));
371913
+ if (magic !== "PAR1") {
371914
+ throw new Error("Invalid Parquet file: missing PAR1 magic number");
371915
+ }
371916
+ const footerBytes = await store2.getRange(`/${partZeroPath}`, {
371917
+ suffixLength: footerLength + TAIL_LENGTH
371918
+ });
371919
+ if (!footerBytes || footerBytes.length !== footerLength + TAIL_LENGTH) {
371920
+ throw new Error(`Failed to load parquet footer bytes for ${parquetPath}`);
371921
+ }
371922
+ return footerBytes;
371923
+ }
371924
+ return null;
371925
+ },
371926
+ meta: { queryClient, store }
371927
+ });
371928
+ }
371929
+ async function _loadParquetMetadataByPart({ queryClient, store }, parquetPath) {
371930
+ return queryClient.fetchQuery({
371931
+ queryKey: ["SpatialDataTableSource", "_loadParquetMetadataByPart", parquetPath],
371932
+ staleTime: Infinity,
371933
+ queryFn: async (ctx) => {
371934
+ const queryClient2 = (
371935
+ /** @type {QueryClient} */
371936
+ ctx.meta?.queryClient
371937
+ );
371938
+ const { readSchema, readMetadata } = await _getParquetModule({ queryClient: queryClient2 });
371939
+ let partIndex = 0;
371940
+ let numParts;
371941
+ const allMetadata = [];
371942
+ do {
371943
+ try {
371944
+ const schemaBytes = await _loadParquetSchemaBytes({ queryClient: queryClient2, store }, parquetPath, partIndex);
371945
+ if (schemaBytes) {
371946
+ const wasmSchema = readSchema(schemaBytes);
371947
+ const arrowTableForSchema = tableFromIPC(wasmSchema.intoIPCStream());
371948
+ const partMetadata = readMetadata(schemaBytes);
371949
+ const partInfo = {
371950
+ schema: arrowTableForSchema,
371951
+ schemaBytes,
371952
+ metadata: partMetadata
371953
+ };
371954
+ allMetadata.push(partInfo);
371955
+ partIndex += 1;
371956
+ }
371957
+ } catch (error2) {
371958
+ if (error2.message.includes("Failed to load parquet footerLength")) {
371959
+ numParts = partIndex;
371960
+ }
371961
+ }
371962
+ } while (numParts === void 0);
371963
+ const metadata2 = {
371964
+ numRows: 0,
371965
+ numRowGroups: 0,
371966
+ numRowsPerGroup: 0,
371967
+ schema: null
371968
+ };
371969
+ if (allMetadata.length > 0) {
371970
+ const firstPart = allMetadata[0];
371971
+ metadata2.numRows = allMetadata.reduce((sum2, part) => sum2 + part.metadata.fileMetadata().numRows(), 0);
371972
+ metadata2.numRowGroups = allMetadata.reduce((sum2, part) => sum2 + part.metadata.numRowGroups(), 0);
371973
+ metadata2.numRowsPerGroup = firstPart.metadata.rowGroup(0).numRows();
371974
+ metadata2.schema = firstPart.schema.schema;
371975
+ }
371976
+ const result = {
371977
+ ...metadata2,
371978
+ // TODO: extract metadata per part and rowGroup into plain objects that match the hyparquet parquetMetadata() return value?
371979
+ // This will also make it easier to test.
371980
+ parts: allMetadata
371981
+ };
371982
+ return result;
371983
+ },
371984
+ meta: { queryClient }
371985
+ });
371986
+ }
371987
+ async function _loadParquetRowGroupByGroupIndex({ queryClient, store }, parquetPath, rowGroupIndex) {
371988
+ return queryClient.fetchQuery({
371989
+ queryKey: ["SpatialDataTableSource", "_loadParquetRowGroupByGroupIndex", parquetPath, rowGroupIndex],
371990
+ staleTime: Infinity,
371991
+ queryFn: async (ctx) => {
371992
+ const queryClient2 = (
371993
+ /** @type {QueryClient} */
371994
+ ctx.meta?.queryClient
371995
+ );
371996
+ const store2 = ctx.meta?.store;
371997
+ const { readParquetRowGroup } = await _getParquetModule({ queryClient: queryClient2 });
371998
+ const allMetadata = await _loadParquetMetadataByPart({ queryClient: queryClient2, store: store2 }, parquetPath);
371999
+ if (rowGroupIndex < 0 || rowGroupIndex >= allMetadata.numRowGroups) {
372000
+ throw new Error(`Row group index ${rowGroupIndex} is out of bounds for parquet table with ${allMetadata.numRowGroups} row groups.`);
372001
+ }
372002
+ let partIndex;
372003
+ let cumulativeRowGroups = 0;
372004
+ for (let i2 = 0; i2 < allMetadata.parts.length; i2++) {
372005
+ const part = allMetadata.parts[i2];
372006
+ const numRowGroupsInPart = part.metadata.numRowGroups();
372007
+ if (rowGroupIndex < cumulativeRowGroups + numRowGroupsInPart) {
372008
+ partIndex = i2;
372009
+ break;
372010
+ }
372011
+ cumulativeRowGroups += numRowGroupsInPart;
372012
+ }
372013
+ if (partIndex === void 0) {
372014
+ throw new Error(`Failed to find part containing row group index ${rowGroupIndex}.`);
372015
+ }
372016
+ const partMetadata = allMetadata.parts[partIndex].metadata;
372017
+ const { schemaBytes } = allMetadata.parts[partIndex];
372018
+ const rowGroupIndexRelativeToPart = rowGroupIndex - cumulativeRowGroups;
372019
+ const rowGroupMetadata = partMetadata.rowGroup(rowGroupIndexRelativeToPart);
372020
+ const rowGroupFileOffset = rowGroupMetadata.fileOffset();
372021
+ const rowGroupCompressedSize = rowGroupMetadata.compressedSize();
372022
+ const rowGroupBytes = await _loadParquetBytes({ queryClient: queryClient2, store: store2 }, parquetPath, { offset: rowGroupFileOffset, length: rowGroupCompressedSize }, partIndex);
372023
+ const rowGroupIPC = readParquetRowGroup(schemaBytes, rowGroupBytes, rowGroupIndexRelativeToPart).intoIPCStream();
372024
+ const rowGroupTable = tableFromIPC(rowGroupIPC);
372025
+ return rowGroupTable;
372026
+ },
372027
+ meta: { queryClient, store }
372028
+ });
372029
+ }
372030
+ async function _loadParquetRowGroupColumnExtent({ queryClient, store }, parquetPath, columnName, rowGroupIndex) {
372031
+ return queryClient.fetchQuery({
372032
+ queryKey: ["SpatialDataTableSource", "_loadParquetRowGroupColumnExtent", parquetPath, columnName, rowGroupIndex],
372033
+ staleTime: Infinity,
372034
+ queryFn: async (ctx) => {
372035
+ const queryClient2 = (
372036
+ /** @type {QueryClient} */
372037
+ ctx.meta?.queryClient
372038
+ );
372039
+ const store2 = ctx.meta?.store;
372040
+ const rowGroupTable = await _loadParquetRowGroupByGroupIndex({ queryClient: queryClient2, store: store2 }, parquetPath, rowGroupIndex);
372041
+ const column2 = rowGroupTable.getChild(columnName);
372042
+ if (!column2) {
372043
+ throw new Error(`Column ${columnName} not found in row group ${rowGroupIndex} of parquet table at ${parquetPath}.`);
372044
+ }
372045
+ if (column2.length === 0) {
372046
+ return { min: null, max: null };
372047
+ }
372048
+ return { min: column2.get(0), max: column2.get(column2.length - 1) };
372049
+ },
372050
+ meta: { queryClient, store }
372051
+ });
372052
+ }
372053
+ function getCachedInRangeSync(queryClient, parquetPath, columnName, lo, hi) {
372054
+ const queryCache = queryClient.getQueryCache();
372055
+ const prevQueries = queryCache.findAll({
372056
+ // Note: Must (manually) keep in sync with queryKey used in _loadParquetRowGroupColumnExtent.
372057
+ queryKey: ["SpatialDataTableSource", "_loadParquetRowGroupColumnExtent", parquetPath, columnName],
372058
+ exact: false
372059
+ });
372060
+ const cachedRowGroupInfo = prevQueries.map((q) => {
372061
+ if (!(q.state.status === "success" && !q.state.isInvalidated)) {
372062
+ return null;
372063
+ }
372064
+ return {
372065
+ queryKey: q.queryKey,
372066
+ index: q.queryKey[4],
372067
+ status: q.state.status,
372068
+ min: q.state.data?.min,
372069
+ max: q.state.data?.max
372070
+ };
372071
+ }).filter((v) => v !== null).toSorted((a2, b2) => a2.index - b2.index);
372072
+ return cachedRowGroupInfo.filter((c2) => c2.index >= lo && c2.index < hi);
372073
+ }
372074
+ async function getCachedInRange(queryClient, parquetPath, columnName, lo, hi) {
372075
+ const queryCache = queryClient.getQueryCache();
372076
+ const prevQueries = queryCache.findAll({
372077
+ // Note: Must (manually) keep in sync with queryKey used in _loadParquetRowGroupColumnExtent.
372078
+ queryKey: ["SpatialDataTableSource", "_loadParquetRowGroupColumnExtent", parquetPath, columnName],
372079
+ exact: false
372080
+ });
372081
+ const cachedRowGroupInfo = prevQueries.map((q) => ({
372082
+ queryKey: q.queryKey,
372083
+ index: q.queryKey[4],
372084
+ status: q.state.status,
372085
+ min: q.state.data?.min,
372086
+ max: q.state.data?.max
372087
+ })).filter((v) => v !== null).toSorted((a2, b2) => a2.index - b2.index);
372088
+ const cachedInRange = cachedRowGroupInfo.filter((c2) => c2.index >= lo && c2.index < hi);
372089
+ const pendingQueries = cachedInRange.filter((c2) => c2.status !== "success");
372090
+ if (pendingQueries.length === 0) {
372091
+ return cachedInRange;
372092
+ }
372093
+ const pendingPromises = pendingQueries.map((c2) => queryClient.ensureQueryData({
372094
+ queryKey: c2.queryKey
372095
+ }));
372096
+ await Promise.all(pendingPromises);
372097
+ return getCachedInRangeSync(queryClient, parquetPath, columnName, lo, hi);
372098
+ }
372099
+ async function _bisectRowGroupsRight({ queryClient, store }, parquetPath, columnName, targetValue) {
372100
+ return queryClient.fetchQuery({
372101
+ queryKey: ["SpatialDataTableSource", "_bisectRowGroupsRight", parquetPath, columnName, targetValue],
372102
+ staleTime: Infinity,
372103
+ queryFn: async (ctx) => {
372104
+ const queryClient2 = (
372105
+ /** @type {QueryClient} */
372106
+ ctx.meta?.queryClient
372107
+ );
372108
+ const store2 = ctx.meta?.store;
372109
+ const allMetadata = await _loadParquetMetadataByPart({ queryClient: queryClient2, store: store2 }, parquetPath);
372110
+ const { numRowGroups } = allMetadata;
372111
+ let lo = 0;
372112
+ let hi = numRowGroups;
372113
+ while (lo < hi) {
372114
+ const cachedInRange = await getCachedInRange(queryClient2, parquetPath, columnName, lo, hi);
372115
+ const betterLo = cachedInRange.slice().reverse().find((c2) => c2.index > lo && targetValue >= c2.max);
372116
+ if (betterLo) {
372117
+ lo = Math.min(hi, betterLo.index + 1);
372118
+ }
372119
+ const betterHi = cachedInRange.find((c2) => targetValue < c2.min);
372120
+ if (betterHi) {
372121
+ hi = Math.max(lo, betterHi.index - 1);
372122
+ }
372123
+ if (lo >= hi) {
372124
+ break;
372125
+ }
372126
+ const mid = Math.floor((lo + hi) / 2);
372127
+ const { max: midVal } = await _loadParquetRowGroupColumnExtent({ queryClient: queryClient2, store: store2 }, parquetPath, columnName, mid);
372128
+ if (midVal === null || targetValue <= midVal) {
372129
+ hi = mid;
372130
+ } else {
372131
+ lo = mid + 1;
372132
+ }
372133
+ }
372134
+ return lo;
372135
+ },
372136
+ meta: { queryClient, store }
372137
+ });
372138
+ }
372139
+ async function _rectToRowGroupIndices({ queryClient, store }, parquetPath, tileBbox, allPointsBbox, mortonCodeColumnName) {
372140
+ return queryClient.fetchQuery({
372141
+ queryKey: ["SpatialDataTableSource", "_rectToRowGroupIndices", parquetPath, tileBbox, allPointsBbox],
372142
+ staleTime: Infinity,
372143
+ queryFn: async (ctx) => {
372144
+ const queryClient2 = (
372145
+ /** @type {QueryClient} */
372146
+ ctx.meta?.queryClient
372147
+ );
372148
+ const store2 = ctx.meta?.store;
372149
+ const mortonIntervals = sdataMortonQueryRectAux(allPointsBbox, [
372150
+ [tileBbox.left, tileBbox.top],
372151
+ // TODO: is this backwards (bottom/top)?
372152
+ [tileBbox.right, tileBbox.bottom]
372153
+ ]);
372154
+ let coveredRowGroupIndices = [];
372155
+ const intervalsSpanMultipleRowGroups = async (startIndex, endIndex) => {
372156
+ if (startIndex > endIndex) {
372157
+ return [false, null];
372158
+ }
372159
+ const [startMin, startMax] = mortonIntervals[startIndex];
372160
+ const [endMin, endMax] = mortonIntervals[endIndex];
372161
+ const [rowGroupIndexMin, rowGroupIndexMax] = await Promise.all([
372162
+ _bisectRowGroupsRight({ queryClient: queryClient2, store: store2 }, parquetPath, mortonCodeColumnName, startMin),
372163
+ _bisectRowGroupsRight({ queryClient: queryClient2, store: store2 }, parquetPath, mortonCodeColumnName, endMax)
372164
+ ]);
372165
+ if (rowGroupIndexMin === rowGroupIndexMax) {
372166
+ return [false, [rowGroupIndexMin]];
372167
+ }
372168
+ if (rowGroupIndexMin + 1 === rowGroupIndexMax || rowGroupIndexMin - 1 === rowGroupIndexMax) {
372169
+ return [false, [rowGroupIndexMin, rowGroupIndexMax]];
372170
+ }
372171
+ return [true, null];
372172
+ };
372173
+ const intervalIndicesToCheck = [[0, mortonIntervals.length - 1]];
372174
+ while (intervalIndicesToCheck.length > 0) {
372175
+ const [startIndex, endIndex] = intervalIndicesToCheck.pop();
372176
+ const [spansMultipleRowGroups, rowGroupIndices] = await intervalsSpanMultipleRowGroups(startIndex, endIndex);
372177
+ if (!spansMultipleRowGroups) {
372178
+ if (rowGroupIndices !== null) {
372179
+ coveredRowGroupIndices = coveredRowGroupIndices.concat(rowGroupIndices);
372180
+ }
372181
+ } else {
372182
+ if (startIndex === endIndex) {
372183
+ const [intervalMin, intervalMax] = mortonIntervals[startIndex];
372184
+ const [rowGroupIndexMin, rowGroupIndexMax] = await Promise.all([
372185
+ _bisectRowGroupsRight({ queryClient: queryClient2, store: store2 }, parquetPath, mortonCodeColumnName, intervalMin),
372186
+ _bisectRowGroupsRight({ queryClient: queryClient2, store: store2 }, parquetPath, mortonCodeColumnName, intervalMax)
372187
+ ]);
372188
+ if (rowGroupIndexMin <= rowGroupIndexMax) {
372189
+ coveredRowGroupIndices = coveredRowGroupIndices.concat(range$e(rowGroupIndexMin, rowGroupIndexMax + 1));
372190
+ } else {
372191
+ coveredRowGroupIndices = coveredRowGroupIndices.concat(range$e(rowGroupIndexMax, rowGroupIndexMin + 1));
372192
+ }
372193
+ } else {
372194
+ const midIndex = Math.floor((startIndex + endIndex) / 2);
372195
+ intervalIndicesToCheck.push([startIndex, midIndex]);
372196
+ intervalIndicesToCheck.push([midIndex + 1, endIndex]);
372197
+ }
372198
+ }
372199
+ }
372200
+ const uniqueCoveredRowGroupIndices = Array.from(new Set(coveredRowGroupIndices));
372201
+ return uniqueCoveredRowGroupIndices;
372202
+ },
372203
+ meta: { queryClient, store }
372204
+ });
371215
372205
  }
371216
372206
  function tableToIndexColumnName(arrowTable) {
371217
372207
  const pandasMetadata = arrowTable.schema.metadata.get("pandas");
@@ -371265,14 +372255,17 @@ function getVarPath(arrPath) {
371265
372255
  class SpatialDataTableSource extends AnnDataSource {
371266
372256
  /**
371267
372257
  *
371268
- * @param {DataSourceParams} params
372258
+ * @param {DataSourceParams & { queryClient: QueryClient }} params
371269
372259
  */
371270
372260
  constructor(params2) {
371271
372261
  super(params2);
372262
+ const { queryClient } = params2;
372263
+ this.queryClient = queryClient;
371272
372264
  this.parquetModulePromise = getParquetModule();
371273
372265
  this.rootAttrs = null;
371274
372266
  this.elementAttrs = {};
371275
372267
  this.parquetTableBytes = {};
372268
+ this.parquetTableIsDirectory = {};
371276
372269
  this.obsIndices = {};
371277
372270
  this.varIndices = {};
371278
372271
  this.varAliases = {};
@@ -371326,17 +372319,19 @@ class SpatialDataTableSource extends AnnDataSource {
371326
372319
  * relative to the store root.
371327
372320
  * @returns {Promise<Uint8Array|undefined>} The parquet file bytes.
371328
372321
  */
371329
- async loadParquetBytes(parquetPath) {
371330
- if (this.parquetTableBytes[parquetPath]) {
371331
- return this.parquetTableBytes[parquetPath];
372322
+ async loadParquetBytes(parquetPath, offset2 = void 0, length2 = void 0, partIndex = void 0) {
372323
+ const { store } = this.storeRoot;
372324
+ let getter2 = (path2) => store.get(path2);
372325
+ if (offset2 !== void 0 && length2 !== void 0 && store.getRange) {
372326
+ getter2 = (path2) => store.getRange(path2, {
372327
+ offset: offset2,
372328
+ length: length2
372329
+ });
371332
372330
  }
371333
- let parquetBytes = await this.storeRoot.store.get(`/${parquetPath}`);
372331
+ let parquetBytes = await getter2(`/${parquetPath}`);
371334
372332
  if (!parquetBytes) {
371335
- const part0Path = `${parquetPath}/part.0.parquet`;
371336
- parquetBytes = await this.storeRoot.store.get(`/${part0Path}`);
371337
- }
371338
- if (parquetBytes) {
371339
- this.parquetTableBytes[parquetPath] = parquetBytes;
372333
+ const part0Path = `${parquetPath}/part.${partIndex ?? 0}.parquet`;
372334
+ parquetBytes = await getter2(`/${part0Path}`);
371340
372335
  }
371341
372336
  return parquetBytes;
371342
372337
  }
@@ -371355,7 +372350,7 @@ class SpatialDataTableSource extends AnnDataSource {
371355
372350
  * @returns {Promise<Uint8Array|null>} The parquet file bytes,
371356
372351
  * or null if the store does not support getRange.
371357
372352
  */
371358
- async loadParquetSchemaBytes(parquetPath) {
372353
+ async loadParquetSchemaBytes(parquetPath, partIndex = void 0) {
371359
372354
  const { store } = this.storeRoot;
371360
372355
  if (store.getRange) {
371361
372356
  const TAIL_LENGTH = 8;
@@ -371363,12 +372358,10 @@ class SpatialDataTableSource extends AnnDataSource {
371363
372358
  let tailBytes = await store.getRange(`/${partZeroPath}`, {
371364
372359
  suffixLength: TAIL_LENGTH
371365
372360
  });
371366
- if (!tailBytes) {
371367
- partZeroPath = `${parquetPath}/part.0.parquet`;
371368
- tailBytes = await store.getRange(`/${partZeroPath}`, {
371369
- suffixLength: TAIL_LENGTH
371370
- });
371371
- }
372361
+ partZeroPath = `${parquetPath}/part.${partIndex ?? 0}.parquet`;
372362
+ tailBytes = await store.getRange(`/${partZeroPath}`, {
372363
+ suffixLength: TAIL_LENGTH
372364
+ });
371372
372365
  if (!tailBytes || tailBytes.length < TAIL_LENGTH) {
371373
372366
  throw new Error(`Failed to load parquet footerLength for ${parquetPath}`);
371374
372367
  }
@@ -371430,7 +372423,7 @@ class SpatialDataTableSource extends AnnDataSource {
371430
372423
  const schemaBytes = await this.loadParquetSchemaBytes(parquetPath);
371431
372424
  if (schemaBytes) {
371432
372425
  const wasmSchema = readSchema(schemaBytes);
371433
- const arrowTableForSchema = await tableFromIPC(wasmSchema.intoIPCStream());
372426
+ const arrowTableForSchema = tableFromIPC(wasmSchema.intoIPCStream());
371434
372427
  indexColumnName = tableToIndexColumnName(arrowTableForSchema);
371435
372428
  }
371436
372429
  } catch (e3) {
@@ -371446,14 +372439,14 @@ class SpatialDataTableSource extends AnnDataSource {
371446
372439
  }
371447
372440
  if (columns2 && !indexColumnName) {
371448
372441
  const wasmSchema = readSchema(parquetBytes);
371449
- const arrowTableForSchema = await tableFromIPC(wasmSchema.intoIPCStream());
372442
+ const arrowTableForSchema = tableFromIPC(wasmSchema.intoIPCStream());
371450
372443
  indexColumnName = tableToIndexColumnName(arrowTableForSchema);
371451
372444
  }
371452
372445
  if (options.columns && indexColumnName) {
371453
372446
  options.columns = [...options.columns, indexColumnName];
371454
372447
  }
371455
372448
  const wasmTable = readParquet(parquetBytes, options);
371456
- const arrowTable = await tableFromIPC(wasmTable.intoIPCStream());
372449
+ const arrowTable = tableFromIPC(wasmTable.intoIPCStream());
371457
372450
  return arrowTable;
371458
372451
  }
371459
372452
  // TABLE-SPECIFIC METHODS
@@ -371517,6 +372510,95 @@ class SpatialDataTableSource extends AnnDataSource {
371517
372510
  this.varAliases[varPath] = this.varAliases[varPath].map((val, ind) => val ? val.concat(` (${index2[ind]})`) : index2[ind]);
371518
372511
  return this.varAliases[varPath];
371519
372512
  }
372513
+ async _supportsTiledPoints(parquetPath, featureIndexColumnName, mortonCodeColumn) {
372514
+ const { queryClient } = this;
372515
+ const { store } = this.storeRoot;
372516
+ const allMetadata = await _loadParquetMetadataByPart({ queryClient, store }, parquetPath);
372517
+ const { numRowsPerGroup } = allMetadata;
372518
+ const numRowsTotal = allMetadata.numRows;
372519
+ if (numRowsPerGroup >= 1e5) {
372520
+ if (numRowsTotal > 5e6) {
372521
+ throw new Error(`The Parquet table at ${parquetPath} has ${numRowsTotal} total rows, which necessitates tiled loading, but it was not possible because the row group size is too large (${numRowsPerGroup}). See the Vitessce documentation at Data Troubleshooting -> Points for more details.`);
372522
+ }
372523
+ return false;
372524
+ }
372525
+ const mortonCodeColumnName = mortonCodeColumn ?? "morton_code_2d";
372526
+ const requiredColumns = ["x", "y", featureIndexColumnName, mortonCodeColumnName];
372527
+ const hasColumns = allMetadata?.schema?.fields?.map((f2) => f2.name);
372528
+ if (!hasColumns) {
372529
+ return false;
372530
+ }
372531
+ const hasRequiredColumns = requiredColumns.every((col) => hasColumns.includes(col));
372532
+ if (!hasRequiredColumns && numRowsTotal > 5e6) {
372533
+ throw new Error(`The Parquet table at ${parquetPath} has ${numRowsTotal} total rows, which necessitates tiled loading, but it was not possible because the required columns are missing. Required columns: ${requiredColumns.join(", ")}. Found columns: ${hasColumns.join(", ")}. See the Vitessce documentation at Data Troubleshooting -> Points for more details.`);
372534
+ }
372535
+ return hasRequiredColumns;
372536
+ }
372537
+ /**
372538
+ * Load point data using a tiled approach.
372539
+ * @param {string} parquetPath A path to a parquet file (or directory).
372540
+ * @param {{ left: number, top: number, right: number, bottom: number }} tileBbox
372541
+ * @param {{ x_min: number, y_min: number, x_max: number, y_max: number }} allPointsBbox
372542
+ * @param {string[]|undefined} columns An optional list of column names to load.
372543
+ * @returns
372544
+ */
372545
+ async loadParquetTableInRect(parquetPath, tileBbox, allPointsBbox, signal, featureIndexColumnName, mortonCodeColumn) {
372546
+ const { queryClient } = this;
372547
+ const { store } = this.storeRoot;
372548
+ const mortonCodeColumnName = mortonCodeColumn ?? "morton_code_2d";
372549
+ const TILE_SIZE2 = 256;
372550
+ let tileBboxes = [];
372551
+ if (tileBbox.right - tileBbox.left > TILE_SIZE2 || tileBbox.bottom - tileBbox.top > TILE_SIZE2) {
372552
+ const xSteps = Math.ceil((tileBbox.right - tileBbox.left) / TILE_SIZE2);
372553
+ const ySteps = Math.ceil((tileBbox.bottom - tileBbox.top) / TILE_SIZE2);
372554
+ const xStepSize = (tileBbox.right - tileBbox.left) / xSteps;
372555
+ const yStepSize = (tileBbox.bottom - tileBbox.top) / ySteps;
372556
+ for (let i2 = 0; i2 < xSteps; i2++) {
372557
+ for (let j = 0; j < ySteps; j++) {
372558
+ const subTileBbox = {
372559
+ left: tileBbox.left + i2 * xStepSize,
372560
+ right: Math.min(tileBbox.left + (i2 + 1) * xStepSize, tileBbox.right),
372561
+ top: tileBbox.top + j * yStepSize,
372562
+ bottom: Math.min(tileBbox.top + (j + 1) * yStepSize, tileBbox.bottom)
372563
+ };
372564
+ tileBboxes.push(subTileBbox);
372565
+ }
372566
+ }
372567
+ } else {
372568
+ tileBboxes = [tileBbox];
372569
+ }
372570
+ const rowGroupIndicesPerTile = await Promise.all(tileBboxes.map(async (subTileBbox) => _rectToRowGroupIndices({ queryClient, store }, parquetPath, subTileBbox, allPointsBbox, mortonCodeColumnName)));
372571
+ const uniqueCoveredRowGroupIndices = Array.from(new Set(rowGroupIndicesPerTile.flat())).toSorted((a2, b2) => a2 - b2);
372572
+ const allMetadata = await _loadParquetMetadataByPart({ queryClient, store }, parquetPath);
372573
+ const { numRowsPerGroup } = allMetadata;
372574
+ const numRowGroups = uniqueCoveredRowGroupIndices.length;
372575
+ const totalNumRows = numRowsPerGroup * numRowGroups;
372576
+ const xArr = new Float32Array(totalNumRows);
372577
+ const yArr = new Float32Array(totalNumRows);
372578
+ const featureIndexArr = new Uint32Array(totalNumRows);
372579
+ const rowGroupTables = await Promise.all(uniqueCoveredRowGroupIndices.map(async (rowGroupIndex) => _loadParquetRowGroupByGroupIndex({ queryClient, store }, parquetPath, rowGroupIndex)));
372580
+ let rowOffset = 0;
372581
+ rowGroupTables.forEach((table2) => {
372582
+ const xColumn = table2.getChild("x");
372583
+ const yColumn = table2.getChild("y");
372584
+ const featureIndexColumn = table2.getChild(featureIndexColumnName);
372585
+ if (!xColumn || !yColumn || !featureIndexColumn) {
372586
+ throw new Error(`Missing required column in parquet table at ${parquetPath}. Required columns: x, y, feature_index`);
372587
+ }
372588
+ xArr.set(xColumn.toArray(), rowOffset);
372589
+ yArr.set(yColumn.toArray(), rowOffset);
372590
+ featureIndexArr.set(featureIndexColumn.toArray(), rowOffset);
372591
+ rowOffset += numRowsPerGroup;
372592
+ });
372593
+ return {
372594
+ data: {
372595
+ x: xArr,
372596
+ y: yArr,
372597
+ featureIndices: featureIndexArr
372598
+ },
372599
+ shape: [3, totalNumRows]
372600
+ };
372601
+ }
371520
372602
  }
371521
372603
  class BaseEvent {
371522
372604
  /**
@@ -377651,7 +378733,7 @@ class SpatialDataPointsSource extends SpatialDataTableSource {
377651
378733
  * shape: [number, number],
377652
378734
  * }>} A promise for a zarr array containing the data.
377653
378735
  */
377654
- async loadPoints(elementPath) {
378736
+ async loadPoints(elementPath, featureIndexColumnNameFromOptions) {
377655
378737
  const parquetPath = getParquetPath(elementPath);
377656
378738
  const zattrs = await this.loadSpatialDataElementAttrs(elementPath);
377657
378739
  const { axes, spatialdata_attrs: spatialDataAttrs } = zattrs;
@@ -377672,6 +378754,35 @@ class SpatialDataPointsSource extends SpatialDataTableSource {
377672
378754
  data: axisColumnArrs
377673
378755
  };
377674
378756
  }
378757
+ /**
378758
+ *
378759
+ * @param {string} elementPath
378760
+ * @param {{ left: number, top: number, right: number, bottom: number }} tileBbox
378761
+ * @returns {Promise<{
378762
+ * data: [ZarrTypedArray<any>, ZarrTypedArray<any>],
378763
+ * shape: [number, number],
378764
+ * }>} A promise for a zarr array containing the data.
378765
+ */
378766
+ async loadPointsInRect(elementPath, tileBbox, signal, featureIndexColumnNameFromOptions, mortonCodeColumn) {
378767
+ const parquetPath = getParquetPath(elementPath);
378768
+ const zattrs = await this.loadSpatialDataElementAttrs(elementPath);
378769
+ const {
378770
+ // axes,
378771
+ spatialdata_attrs: spatialDataAttrs,
378772
+ // The bounding box (extent) of all points.
378773
+ // Required for un-normalization from uints back to floats.
378774
+ // TODO: decide whether these will be stored here or somewhere else.
378775
+ // Reference: https://github.com/vitessce/vitessce-python/pull/476#issuecomment-3362656956
378776
+ bounding_box: allPointsBbox
378777
+ } = zattrs;
378778
+ const { feature_key: featureKey } = spatialDataAttrs;
378779
+ const featureIndexColumnName = featureIndexColumnNameFromOptions ?? `${featureKey}_codes`;
378780
+ return this.loadParquetTableInRect(parquetPath, tileBbox, allPointsBbox, signal, featureIndexColumnName, mortonCodeColumn);
378781
+ }
378782
+ async supportsLoadPointsInRect(elementPath, featureIndexColumnName, mortonCodeColumn) {
378783
+ const parquetPath = getParquetPath(elementPath);
378784
+ return this._supportsTiledPoints(parquetPath, featureIndexColumnName, mortonCodeColumn);
378785
+ }
377675
378786
  }
377676
378787
  class OmeTiffSource {
377677
378788
  constructor({ url, requestInit: requestInit2 }) {
@@ -383776,7 +384887,20 @@ const baseCoordinationTypes = [
383776
384887
  path: obsSetPath,
383777
384888
  color: rgbArray
383778
384889
  })).nullable()),
383779
- new PluginCoordinationType(CoordinationType$1.OBS_COLOR_ENCODING, "cellSetSelection", z.enum(["geneSelection", "cellSetSelection", "spatialChannelColor", "spatialLayerColor", "obsLabels"])),
384890
+ new PluginCoordinationType(CoordinationType$1.FEATURE_COLOR, null, z.array(z.object({
384891
+ name: z.string(),
384892
+ color: rgbArray
384893
+ })).nullable()),
384894
+ new PluginCoordinationType(CoordinationType$1.OBS_COLOR_ENCODING, "cellSetSelection", z.enum([
384895
+ "geneSelection",
384896
+ "cellSetSelection",
384897
+ "spatialChannelColor",
384898
+ "spatialLayerColor",
384899
+ "obsLabels",
384900
+ // For point coloring.
384901
+ "random",
384902
+ "randomByFeature"
384903
+ ])),
383780
384904
  new PluginCoordinationType(CoordinationType$1.FEATURE_FILTER, null, z.array(z.string()).nullable()),
383781
384905
  new PluginCoordinationType(CoordinationType$1.FEATURE_HIGHLIGHT, null, z.string().nullable()),
383782
384906
  new PluginCoordinationType(CoordinationType$1.FEATURE_SELECTION, null, z.array(z.string()).nullable()),
@@ -383794,7 +384918,7 @@ const baseCoordinationTypes = [
383794
384918
  new PluginCoordinationType(CoordinationType$1.FEATURE_SET_SELECTION, null, z.array(obsSetPath).nullable()),
383795
384919
  new PluginCoordinationType(CoordinationType$1.FEATURE_SELECTION_MODE, null, z.enum(["featureSelection", "featureSetSelection"]).nullable()),
383796
384920
  new PluginCoordinationType(CoordinationType$1.FEATURE_SET_FILTER, null, z.array(obsSetPath).nullable()),
383797
- new PluginCoordinationType(CoordinationType$1.FEATURE_FILTER_MODE, null, z.enum(["featureFilter", "featureSetFilter"]).nullable()),
384921
+ new PluginCoordinationType(CoordinationType$1.FEATURE_FILTER_MODE, null, z.enum(["featureFilter", "featureSetFilter", "featureSelection"]).nullable()),
383798
384922
  new PluginCoordinationType(CoordinationType$1.FEATURE_VALUE_TRANSFORM, null, z.enum(["log1p", "arcsinh"]).nullable()),
383799
384923
  new PluginCoordinationType(CoordinationType$1.FEATURE_VALUE_TRANSFORM_COEFFICIENT, 1, z.number()),
383800
384924
  new PluginCoordinationType(CoordinationType$1.FEATURE_VALUE_POSITIVITY_THRESHOLD, 0, z.number()),