@vitessce/launcher 3.8.13 → 3.9.0

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