@vitessce/all 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, { useContext, forwardRef as forwardRef$1, useRef, useMemo as useMemo$1, createContext, createElement, isValidElement, cloneElement, Children, version as version$6, useLayoutEffect as useLayoutEffect$1, useEffect, useImperativeHandle, PureComponent, Component as Component$1, useState, useCallback, 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 } 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 } 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) {
@@ -9354,6 +9354,7 @@ const CoordinationType$1 = {
9354
9354
  OBS_SET_HIGHLIGHT: "obsSetHighlight",
9355
9355
  OBS_SET_EXPANSION: "obsSetExpansion",
9356
9356
  OBS_SET_COLOR: "obsSetColor",
9357
+ FEATURE_COLOR: "featureColor",
9357
9358
  FEATURE_HIGHLIGHT: "featureHighlight",
9358
9359
  FEATURE_SELECTION: "featureSelection",
9359
9360
  FEATURE_SET_SELECTION: "featureSetSelection",
@@ -9958,6 +9959,8 @@ const COMPONENT_COORDINATION_TYPES = {
9958
9959
  CoordinationType$1.OBS_SET_FILTER,
9959
9960
  CoordinationType$1.OBS_SET_HIGHLIGHT,
9960
9961
  CoordinationType$1.OBS_SET_COLOR,
9962
+ CoordinationType$1.FEATURE_COLOR,
9963
+ CoordinationType$1.FEATURE_FILTER_MODE,
9961
9964
  CoordinationType$1.FEATURE_HIGHLIGHT,
9962
9965
  CoordinationType$1.FEATURE_SELECTION,
9963
9966
  CoordinationType$1.FEATURE_VALUE_COLORMAP,
@@ -10172,6 +10175,8 @@ const COMPONENT_COORDINATION_TYPES = {
10172
10175
  CoordinationType$1.SPATIAL_SPOT_STROKE_WIDTH,
10173
10176
  CoordinationType$1.SPATIAL_LAYER_COLOR,
10174
10177
  CoordinationType$1.OBS_COLOR_ENCODING,
10178
+ CoordinationType$1.FEATURE_COLOR,
10179
+ CoordinationType$1.FEATURE_FILTER_MODE,
10175
10180
  CoordinationType$1.FEATURE_VALUE_COLORMAP,
10176
10181
  CoordinationType$1.FEATURE_VALUE_COLORMAP_RANGE,
10177
10182
  CoordinationType$1.FEATURE_SELECTION,
@@ -31993,7 +31998,7 @@ var _span$3;
31993
31998
  const useUtilityClasses$1h = (ownerState) => {
31994
31999
  const {
31995
32000
  classes: classes2,
31996
- contained,
32001
+ contained: contained2,
31997
32002
  size: size2,
31998
32003
  disabled,
31999
32004
  error: error2,
@@ -32002,7 +32007,7 @@ const useUtilityClasses$1h = (ownerState) => {
32002
32007
  required: required2
32003
32008
  } = ownerState;
32004
32009
  const slots = {
32005
- root: ["root", disabled && "disabled", error2 && "error", size2 && `size${capitalize$1(size2)}`, contained && "contained", focused && "focused", filled && "filled", required2 && "required"]
32010
+ root: ["root", disabled && "disabled", error2 && "error", size2 && `size${capitalize$1(size2)}`, contained2 && "contained", focused && "focused", filled && "filled", required2 && "required"]
32006
32011
  };
32007
32012
  return composeClasses(slots, getFormHelperTextUtilityClasses, classes2);
32008
32013
  };
@@ -33495,6 +33500,90 @@ ListItemText.propTypes = {
33495
33500
  */
33496
33501
  sx: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), PropTypes.func, PropTypes.object])
33497
33502
  };
33503
+ function getListItemIconUtilityClass(slot) {
33504
+ return generateUtilityClass("MuiListItemIcon", slot);
33505
+ }
33506
+ const listItemIconClasses = generateUtilityClasses("MuiListItemIcon", ["root", "alignItemsFlexStart"]);
33507
+ const useUtilityClasses$1b = (ownerState) => {
33508
+ const {
33509
+ alignItems,
33510
+ classes: classes2
33511
+ } = ownerState;
33512
+ const slots = {
33513
+ root: ["root", alignItems === "flex-start" && "alignItemsFlexStart"]
33514
+ };
33515
+ return composeClasses(slots, getListItemIconUtilityClass, classes2);
33516
+ };
33517
+ const ListItemIconRoot = styled("div", {
33518
+ name: "MuiListItemIcon",
33519
+ slot: "Root",
33520
+ overridesResolver: (props, styles2) => {
33521
+ const {
33522
+ ownerState
33523
+ } = props;
33524
+ return [styles2.root, ownerState.alignItems === "flex-start" && styles2.alignItemsFlexStart];
33525
+ }
33526
+ })(memoTheme(({
33527
+ theme
33528
+ }) => ({
33529
+ minWidth: 56,
33530
+ color: (theme.vars || theme).palette.action.active,
33531
+ flexShrink: 0,
33532
+ display: "inline-flex",
33533
+ variants: [{
33534
+ props: {
33535
+ alignItems: "flex-start"
33536
+ },
33537
+ style: {
33538
+ marginTop: 8
33539
+ }
33540
+ }]
33541
+ })));
33542
+ const ListItemIcon = /* @__PURE__ */ React.forwardRef(function ListItemIcon2(inProps, ref2) {
33543
+ const props = useDefaultProps({
33544
+ props: inProps,
33545
+ name: "MuiListItemIcon"
33546
+ });
33547
+ const {
33548
+ className,
33549
+ ...other
33550
+ } = props;
33551
+ const context2 = React.useContext(ListContext);
33552
+ const ownerState = {
33553
+ ...props,
33554
+ alignItems: context2.alignItems
33555
+ };
33556
+ const classes2 = useUtilityClasses$1b(ownerState);
33557
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(ListItemIconRoot, {
33558
+ className: clsx$1(classes2.root, className),
33559
+ ownerState,
33560
+ ref: ref2,
33561
+ ...other
33562
+ });
33563
+ });
33564
+ ListItemIcon.propTypes = {
33565
+ // ┌────────────────────────────── Warning ──────────────────────────────┐
33566
+ // │ These PropTypes are generated from the TypeScript type definitions. │
33567
+ // │ To update them, edit the d.ts file and run `pnpm proptypes`. │
33568
+ // └─────────────────────────────────────────────────────────────────────┘
33569
+ /**
33570
+ * The content of the component, normally `Icon`, `SvgIcon`,
33571
+ * or a `@mui/icons-material` SVG icon element.
33572
+ */
33573
+ children: PropTypes.node,
33574
+ /**
33575
+ * Override or extend the styles applied to the component.
33576
+ */
33577
+ classes: PropTypes.object,
33578
+ /**
33579
+ * @ignore
33580
+ */
33581
+ className: PropTypes.string,
33582
+ /**
33583
+ * The system prop that allows defining system overrides as well as additional CSS styles.
33584
+ */
33585
+ sx: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), PropTypes.func, PropTypes.object])
33586
+ };
33498
33587
  function getScrollbarSize$1(win2 = window) {
33499
33588
  const documentWidth = win2.document.documentElement.clientWidth;
33500
33589
  return win2.innerWidth - documentWidth;
@@ -33746,7 +33835,7 @@ function getDividerUtilityClass(slot) {
33746
33835
  return generateUtilityClass("MuiDivider", slot);
33747
33836
  }
33748
33837
  const dividerClasses = generateUtilityClasses("MuiDivider", ["root", "absolute", "fullWidth", "inset", "middle", "flexItem", "light", "vertical", "withChildren", "withChildrenVertical", "textAlignRight", "textAlignLeft", "wrapper", "wrapperVertical"]);
33749
- const useUtilityClasses$1b = (ownerState) => {
33838
+ const useUtilityClasses$1a = (ownerState) => {
33750
33839
  const {
33751
33840
  absolute: absolute2,
33752
33841
  children: children3,
@@ -33960,7 +34049,7 @@ const Divider$1 = /* @__PURE__ */ React.forwardRef(function Divider2(inProps, re
33960
34049
  textAlign: textAlign2,
33961
34050
  variant
33962
34051
  };
33963
- const classes2 = useUtilityClasses$1b(ownerState);
34052
+ const classes2 = useUtilityClasses$1a(ownerState);
33964
34053
  return /* @__PURE__ */ jsxRuntimeExports.jsx(DividerRoot, {
33965
34054
  as: component,
33966
34055
  className: clsx$1(classes2.root, className),
@@ -34042,90 +34131,6 @@ Divider$1.propTypes = {
34042
34131
  */
34043
34132
  variant: PropTypes.oneOfType([PropTypes.oneOf(["fullWidth", "inset", "middle"]), PropTypes.string])
34044
34133
  };
34045
- function getListItemIconUtilityClass(slot) {
34046
- return generateUtilityClass("MuiListItemIcon", slot);
34047
- }
34048
- const listItemIconClasses = generateUtilityClasses("MuiListItemIcon", ["root", "alignItemsFlexStart"]);
34049
- const useUtilityClasses$1a = (ownerState) => {
34050
- const {
34051
- alignItems,
34052
- classes: classes2
34053
- } = ownerState;
34054
- const slots = {
34055
- root: ["root", alignItems === "flex-start" && "alignItemsFlexStart"]
34056
- };
34057
- return composeClasses(slots, getListItemIconUtilityClass, classes2);
34058
- };
34059
- const ListItemIconRoot = styled("div", {
34060
- name: "MuiListItemIcon",
34061
- slot: "Root",
34062
- overridesResolver: (props, styles2) => {
34063
- const {
34064
- ownerState
34065
- } = props;
34066
- return [styles2.root, ownerState.alignItems === "flex-start" && styles2.alignItemsFlexStart];
34067
- }
34068
- })(memoTheme(({
34069
- theme
34070
- }) => ({
34071
- minWidth: 56,
34072
- color: (theme.vars || theme).palette.action.active,
34073
- flexShrink: 0,
34074
- display: "inline-flex",
34075
- variants: [{
34076
- props: {
34077
- alignItems: "flex-start"
34078
- },
34079
- style: {
34080
- marginTop: 8
34081
- }
34082
- }]
34083
- })));
34084
- const ListItemIcon = /* @__PURE__ */ React.forwardRef(function ListItemIcon2(inProps, ref2) {
34085
- const props = useDefaultProps({
34086
- props: inProps,
34087
- name: "MuiListItemIcon"
34088
- });
34089
- const {
34090
- className,
34091
- ...other
34092
- } = props;
34093
- const context2 = React.useContext(ListContext);
34094
- const ownerState = {
34095
- ...props,
34096
- alignItems: context2.alignItems
34097
- };
34098
- const classes2 = useUtilityClasses$1a(ownerState);
34099
- return /* @__PURE__ */ jsxRuntimeExports.jsx(ListItemIconRoot, {
34100
- className: clsx$1(classes2.root, className),
34101
- ownerState,
34102
- ref: ref2,
34103
- ...other
34104
- });
34105
- });
34106
- ListItemIcon.propTypes = {
34107
- // ┌────────────────────────────── Warning ──────────────────────────────┐
34108
- // │ These PropTypes are generated from the TypeScript type definitions. │
34109
- // │ To update them, edit the d.ts file and run `pnpm proptypes`. │
34110
- // └─────────────────────────────────────────────────────────────────────┘
34111
- /**
34112
- * The content of the component, normally `Icon`, `SvgIcon`,
34113
- * or a `@mui/icons-material` SVG icon element.
34114
- */
34115
- children: PropTypes.node,
34116
- /**
34117
- * Override or extend the styles applied to the component.
34118
- */
34119
- classes: PropTypes.object,
34120
- /**
34121
- * @ignore
34122
- */
34123
- className: PropTypes.string,
34124
- /**
34125
- * The system prop that allows defining system overrides as well as additional CSS styles.
34126
- */
34127
- sx: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), PropTypes.func, PropTypes.object])
34128
- };
34129
34134
  function getMenuItemUtilityClass(slot) {
34130
34135
  return generateUtilityClass("MuiMenuItem", slot);
34131
34136
  }
@@ -169936,14 +169941,14 @@ class RBush {
169936
169941
  search(bbox2) {
169937
169942
  let node2 = this.data;
169938
169943
  const result = [];
169939
- if (!intersects$3(bbox2, node2)) return result;
169944
+ if (!intersects$4(bbox2, node2)) return result;
169940
169945
  const toBBox = this.toBBox;
169941
169946
  const nodesToSearch = [];
169942
169947
  while (node2) {
169943
169948
  for (let i2 = 0; i2 < node2.children.length; i2++) {
169944
169949
  const child = node2.children[i2];
169945
169950
  const childBBox = node2.leaf ? toBBox(child) : child;
169946
- if (intersects$3(bbox2, childBBox)) {
169951
+ if (intersects$4(bbox2, childBBox)) {
169947
169952
  if (node2.leaf) result.push(child);
169948
169953
  else if (contains$3(bbox2, childBBox)) this._all(child, result);
169949
169954
  else nodesToSearch.push(child);
@@ -169955,13 +169960,13 @@ class RBush {
169955
169960
  }
169956
169961
  collides(bbox2) {
169957
169962
  let node2 = this.data;
169958
- if (!intersects$3(bbox2, node2)) return false;
169963
+ if (!intersects$4(bbox2, node2)) return false;
169959
169964
  const nodesToSearch = [];
169960
169965
  while (node2) {
169961
169966
  for (let i2 = 0; i2 < node2.children.length; i2++) {
169962
169967
  const child = node2.children[i2];
169963
169968
  const childBBox = node2.leaf ? this.toBBox(child) : child;
169964
- if (intersects$3(bbox2, childBBox)) {
169969
+ if (intersects$4(bbox2, childBBox)) {
169965
169970
  if (node2.leaf || contains$3(bbox2, childBBox)) return true;
169966
169971
  nodesToSearch.push(child);
169967
169972
  }
@@ -170273,7 +170278,7 @@ function intersectionArea(a2, b2) {
170273
170278
  function contains$3(a2, b2) {
170274
170279
  return a2.minX <= b2.minX && a2.minY <= b2.minY && b2.maxX <= a2.maxX && b2.maxY <= a2.maxY;
170275
170280
  }
170276
- function intersects$3(a2, b2) {
170281
+ function intersects$4(a2, b2) {
170277
170282
  return b2.minX <= a2.maxX && b2.minY <= a2.maxY && b2.maxX >= a2.minX && b2.maxY >= a2.minY;
170278
170283
  }
170279
170284
  function createNode(children3) {
@@ -209135,22 +209140,22 @@ async function getDecoder(fileDirectory) {
209135
209140
  const Decoder = await importFn();
209136
209141
  return new Decoder(fileDirectory);
209137
209142
  }
209138
- addDecoder([void 0, 1], () => import("./raw-BEDGxpo9.js").then((m2) => m2.default));
209139
- addDecoder(5, () => import("./lzw-CkA9e76s.js").then((m2) => m2.default));
209143
+ addDecoder([void 0, 1], () => import("./raw-C5wVKuiu.js").then((m2) => m2.default));
209144
+ addDecoder(5, () => import("./lzw-BNtf5UsG.js").then((m2) => m2.default));
209140
209145
  addDecoder(6, () => {
209141
209146
  throw new Error("old style JPEG compression is not supported.");
209142
209147
  });
209143
- addDecoder(7, () => import("./jpeg-BOKb2qaL.js").then((m2) => m2.default));
209144
- addDecoder([8, 32946], () => import("./deflate-qFZzl85K.js").then((m2) => m2.default));
209145
- addDecoder(32773, () => import("./packbits-DOr_NbG_.js").then((m2) => m2.default));
209148
+ addDecoder(7, () => import("./jpeg-C90IvgAr.js").then((m2) => m2.default));
209149
+ addDecoder([8, 32946], () => import("./deflate-mmlLWSqG.js").then((m2) => m2.default));
209150
+ addDecoder(32773, () => import("./packbits-BaMXgBB6.js").then((m2) => m2.default));
209146
209151
  addDecoder(
209147
209152
  34887,
209148
- () => import("./lerc-CBp5l3Pw.js").then(async (m2) => {
209153
+ () => import("./lerc-cm5d7rjy.js").then(async (m2) => {
209149
209154
  await m2.zstd.init();
209150
209155
  return m2;
209151
209156
  }).then((m2) => m2.default)
209152
209157
  );
209153
- addDecoder(50001, () => import("./webimage-Ad_VhFBm.js").then((m2) => m2.default));
209158
+ addDecoder(50001, () => import("./webimage-BjAUnP6y.js").then((m2) => m2.default));
209154
209159
  function copyNewSize(array2, width2, height2, samplesPerPixel = 1) {
209155
209160
  return new (Object.getPrototypeOf(array2)).constructor(width2 * height2 * samplesPerPixel);
209156
209161
  }
@@ -231309,6 +231314,7 @@ function Legend(props) {
231309
231314
  positionRelative = false,
231310
231315
  highContrast = false,
231311
231316
  obsType,
231317
+ isPointsLayer = false,
231312
231318
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
231313
231319
  featureType: _featureType = void 0,
231314
231320
  // Unused but accepted for API compatibility
@@ -231316,6 +231322,9 @@ function Legend(props) {
231316
231322
  considerSelections = true,
231317
231323
  obsColorEncoding,
231318
231324
  featureSelection,
231325
+ featureFilterMode,
231326
+ featureColor,
231327
+ featureIndex,
231319
231328
  featureLabelsMap,
231320
231329
  featureValueColormap,
231321
231330
  featureValueColormapRange,
@@ -231359,15 +231368,94 @@ function Legend(props) {
231359
231368
  debouncedSetRange(rangeValue);
231360
231369
  }
231361
231370
  }, [debouncedSetRange]);
231371
+ const obsLabel = capitalize$3(obsType ?? null);
231362
231372
  const isDarkTheme = theme === "dark";
231363
- const isStaticColor = obsColorEncoding === "spatialChannelColor" || obsColorEncoding === "spatialLayerColor";
231364
- const isSetColor = obsColorEncoding === "cellSetSelection";
231373
+ const isStaticColor = !isPointsLayer && (obsColorEncoding === "spatialChannelColor" || obsColorEncoding === "spatialLayerColor");
231374
+ const isSetColor = !isPointsLayer && obsColorEncoding === "cellSetSelection";
231365
231375
  const layerColor = Array.isArray(spatialLayerColor) && spatialLayerColor.length === 3 ? spatialLayerColor : getDefaultColor(theme ?? "light");
231366
231376
  const channelColor = Array.isArray(spatialChannelColor) && spatialChannelColor.length === 3 ? spatialChannelColor : getDefaultColor(theme ?? "light");
231367
231377
  const staticColor = obsColorEncoding === "spatialChannelColor" ? channelColor : layerColor;
231368
- 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);
231378
+ 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);
231379
+ const pointsLegendElements = [];
231380
+ if (isPointsLayer) {
231381
+ const MAX_NUM_COLORS = 10;
231382
+ const hasFeatureSelection = Array.isArray(featureSelection) && featureSelection.length > 0;
231383
+ const showUnselected = featureFilterMode !== "featureSelection";
231384
+ if (obsColorEncoding === "spatialLayerColor") {
231385
+ if (!hasFeatureSelection) {
231386
+ pointsLegendElements.push({
231387
+ name: obsLabel,
231388
+ color: staticColor
231389
+ });
231390
+ } else {
231391
+ const limitedFeatureSelection = featureSelection.slice(0, MAX_NUM_COLORS);
231392
+ limitedFeatureSelection.forEach((featureName) => {
231393
+ pointsLegendElements.push({
231394
+ name: featureName,
231395
+ color: staticColor
231396
+ });
231397
+ });
231398
+ }
231399
+ } else if (obsColorEncoding === "geneSelection") {
231400
+ if (!hasFeatureSelection) {
231401
+ pointsLegendElements.push({
231402
+ name: obsLabel,
231403
+ color: staticColor
231404
+ });
231405
+ } else {
231406
+ const limitedFeatureSelection = featureSelection.slice(0, MAX_NUM_COLORS);
231407
+ limitedFeatureSelection.forEach((featureName) => {
231408
+ const featureColorMatch = Array.isArray(featureColor) ? featureColor.find((fc) => fc.name === featureName)?.color : null;
231409
+ pointsLegendElements.push({
231410
+ name: featureName,
231411
+ // If no color is specified for this feature, use staticColor.
231412
+ color: featureColorMatch ?? staticColor
231413
+ });
231414
+ });
231415
+ }
231416
+ } else if (obsColorEncoding === "randomByFeature") {
231417
+ if (!hasFeatureSelection) {
231418
+ pointsLegendElements.push({
231419
+ name: obsLabel,
231420
+ // For now, using black and white for this.
231421
+ // (It should not match any color in PALETTE)
231422
+ color: isDarkTheme ? [255, 255, 255] : [0, 0, 0]
231423
+ });
231424
+ } else {
231425
+ const limitedFeatureSelection = featureSelection.slice(0, MAX_NUM_COLORS);
231426
+ limitedFeatureSelection.forEach((featureName) => {
231427
+ const varIndex = (featureIndex ?? []).indexOf(featureName);
231428
+ const featureColorMatch = varIndex >= 0 ? PALETTE[varIndex % PALETTE.length] : null;
231429
+ pointsLegendElements.push({
231430
+ name: featureName,
231431
+ // If no color is specified for this feature, use staticColor.
231432
+ color: featureColorMatch ?? staticColor
231433
+ });
231434
+ });
231435
+ }
231436
+ } else if (obsColorEncoding === "random") {
231437
+ pointsLegendElements.push({
231438
+ name: obsLabel,
231439
+ // For now, using black and white for this.
231440
+ // (It should not match any color in PALETTE)
231441
+ color: isDarkTheme ? [255, 255, 255] : [0, 0, 0]
231442
+ });
231443
+ }
231444
+ if (showUnselected) {
231445
+ pointsLegendElements.push({
231446
+ name: "Unselected",
231447
+ color: getDefaultColor(theme ?? "light")
231448
+ });
231449
+ }
231450
+ }
231369
231451
  const levelZeroNames = useMemo$1(() => Array.from(new Set(obsSetSelection?.map((setPath) => setPath[0]) || [])), [obsSetSelection]);
231370
- const dynamicHeight = isSetColor && obsSetSelection ? levelZeroNames.length * titleHeight + (obsSetSelection?.length ?? 0) * (rectHeight + rectMarginY) : height2 + (!pointsVisible && contoursVisible ? 25 : 0);
231452
+ const dynamicHeight = isPointsLayer ? (
231453
+ // Height logic for points layers.
231454
+ pointsLegendElements.length * (rectHeight + rectMarginY) + titleHeight
231455
+ ) : (
231456
+ // Height logic for non-points layers.
231457
+ isSetColor && obsSetSelection ? levelZeroNames.length * titleHeight + (obsSetSelection?.length ?? 0) * (rectHeight + rectMarginY) : height2 + (!pointsVisible && contoursVisible ? 25 : 0)
231458
+ );
231371
231459
  const availHeight = maxHeight2 !== null ? Math.max(0, maxHeight2 - 4) : Infinity;
231372
231460
  const needsScroll = Number.isFinite(availHeight) && dynamicHeight > availHeight + 1;
231373
231461
  useEffect(() => {
@@ -231379,8 +231467,8 @@ function Legend(props) {
231379
231467
  svg2.selectAll("g").remove();
231380
231468
  svg2.attr("width", width2).attr("height", dynamicHeight);
231381
231469
  const g2 = svg2.append("g").attr("width", width2).attr("height", dynamicHeight);
231382
- const showInteractiveSlider2 = setFeatureValueColormapRange && ["geneSelection", "geneExpression"].includes(obsColorEncoding ?? "") && pointsVisible && featureValueColormap;
231383
- if (!considerSelections || ["geneSelection", "geneExpression"].includes(obsColorEncoding ?? "")) {
231470
+ const showInteractiveSlider2 = !isPointsLayer && setFeatureValueColormapRange && ["geneSelection", "geneExpression"].includes(obsColorEncoding ?? "") && pointsVisible && featureValueColormap;
231471
+ if (!isPointsLayer && (!considerSelections || ["geneSelection", "geneExpression"].includes(obsColorEncoding ?? ""))) {
231384
231472
  const combinedExtent = combineExtents(extent2 ?? null, featureAggregationStrategy ?? null) || [0, 1];
231385
231473
  const [xMin, xMax] = combinedExtent;
231386
231474
  if (featureValueColormap && pointsVisible) {
@@ -231440,10 +231528,10 @@ function Legend(props) {
231440
231528
  });
231441
231529
  }
231442
231530
  }
231443
- if (isStaticColor) {
231531
+ if (!isPointsLayer && isStaticColor) {
231444
231532
  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]})`);
231445
231533
  }
231446
- if (isSetColor && obsSetSelection && obsSetColor) {
231534
+ if (!isPointsLayer && isSetColor && obsSetSelection && obsSetColor) {
231447
231535
  const obsSetSelectionByLevelZero = {};
231448
231536
  obsSetSelection.forEach((setPath) => {
231449
231537
  const levelZeroName = setPath[0];
@@ -231464,6 +231552,16 @@ function Legend(props) {
231464
231552
  });
231465
231553
  });
231466
231554
  }
231555
+ if (isPointsLayer) {
231556
+ let y2 = 0;
231557
+ 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);
231558
+ y2 += titleHeight;
231559
+ pointsLegendElements.forEach(({ name: name2, color: color2 }) => {
231560
+ g2.append("rect").attr("x", 0).attr("y", y2).attr("width", rectHeight).attr("height", rectHeight).attr("fill", `rgb(${color2[0]},${color2[1]},${color2[2]})`);
231561
+ 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);
231562
+ y2 += rectHeight + rectMarginY;
231563
+ });
231564
+ }
231467
231565
  const featureSelectionLabelRaw = featureSelection && featureSelection.length >= 1 && !isStaticColor ? featureSelection.map((geneName) => featureLabelsMap?.get(geneName) || featureLabelsMap?.get(cleanFeatureId(geneName)) || geneName) : null;
231468
231566
  let featureSelectionLabelRawStr = "";
231469
231567
  if (featureAggregationStrategy === "first") {
@@ -231490,12 +231588,11 @@ function Legend(props) {
231490
231588
  }
231491
231589
  const combinedMissing = combineMissings(missing ?? null, featureAggregationStrategy ?? null);
231492
231590
  const featureSelectionLabel = combinedMissing ? `${featureSelectionLabelRawStr} (${Math.round(combinedMissing * 100)}% NaN)` : featureSelectionLabelRawStr;
231493
- const obsLabel = capitalize$3(obsType ?? null);
231494
231591
  const featureLabel = considerSelections ? featureSelectionLabel || capitalize$3(featureValueType ?? null) : capitalize$3(featureValueType ?? null);
231495
231592
  const mainLabel = showObsLabel ? obsLabel : featureLabel;
231496
231593
  const subLabel = showObsLabel ? featureLabel : null;
231497
231594
  const hasSubLabel = subLabel !== null;
231498
- if (!isSetColor) {
231595
+ if (!isPointsLayer && !isSetColor) {
231499
231596
  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);
231500
231597
  if (hasSubLabel) {
231501
231598
  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);
@@ -231534,7 +231631,7 @@ function Legend(props) {
231534
231631
  showObsLabel,
231535
231632
  staticColor
231536
231633
  ]);
231537
- const showInteractiveSlider = setFeatureValueColormapRange && ["geneSelection", "geneExpression"].includes(obsColorEncoding ?? "") && pointsVisible && featureValueColormap;
231634
+ const showInteractiveSlider = !isPointsLayer && setFeatureValueColormapRange && ["geneSelection", "geneExpression"].includes(obsColorEncoding ?? "") && pointsVisible && featureValueColormap;
231538
231635
  const globalExtent = useMemo$1(() => {
231539
231636
  const combined = combineExtents(extent2 ?? null, featureAggregationStrategy ?? null);
231540
231637
  return combined || [0, 1];
@@ -231607,7 +231704,8 @@ function MultiLegend(props) {
231607
231704
  spotMultiFeatureLabels,
231608
231705
  // Points
231609
231706
  pointLayerScopes,
231610
- pointLayerCoordination
231707
+ pointLayerCoordination,
231708
+ pointMultiIndicesData
231611
231709
  } = props;
231612
231710
  const { classes: classes2 } = useStyles$o();
231613
231711
  const reversedSegmentationLayerScopes = useMemo$1(() => [...segmentationLayerScopes || []].reverse(), [segmentationLayerScopes]);
@@ -231618,7 +231716,8 @@ function MultiLegend(props) {
231618
231716
  const layerSetters = pointLayerCoordination?.[1]?.[layerScope];
231619
231717
  if (!layerCoordination)
231620
231718
  return null;
231621
- const { spatialLayerVisible, obsColorEncoding, obsType, featureType, featureValueType, featureSelection, featureValueColormap, featureValueColormapRange, spatialLayerColor, legendVisible } = layerCoordination;
231719
+ const { spatialLayerVisible, obsColorEncoding, obsType, featureType, featureValueType, featureSelection, featureColor, featureFilterMode, featureValueColormap, featureValueColormapRange, spatialLayerColor, legendVisible } = layerCoordination;
231720
+ const pointIndicesData = pointMultiIndicesData?.[layerScope];
231622
231721
  const { setFeatureValueColormapRange } = layerSetters || {};
231623
231722
  const isStaticColor = obsColorEncoding === "spatialLayerColor";
231624
231723
  const height2 = isStaticColor ? 20 : 36;
@@ -231635,6 +231734,10 @@ function MultiLegend(props) {
231635
231734
  obsColorEncoding,
231636
231735
  spatialLayerColor,
231637
231736
  featureSelection,
231737
+ featureFilterMode,
231738
+ featureColor,
231739
+ featureIndex: pointIndicesData?.featureIndex,
231740
+ isPointsLayer: true,
231638
231741
  // featureLabelsMap={featureLabelsMap} // TODO
231639
231742
  featureValueColormap: featureValueColormap || "viridis",
231640
231743
  featureValueColormapRange: featureValueColormapRange || [0, 1],
@@ -252046,6 +252149,7 @@ class Spatial2 extends AbstractSpatialOrScatterplot {
252046
252149
  this.obsSpotsQuadTree = {};
252047
252150
  this.obsPointsData = {};
252048
252151
  this.obsPointsQuadTree = {};
252152
+ this.obsPointsLoadingStatus = {};
252049
252153
  this.imageLayers = [];
252050
252154
  this.obsSegmentationsLayers = [];
252051
252155
  this.obsSpotsLayers = [];
@@ -252226,10 +252330,131 @@ class Spatial2 extends AbstractSpatialOrScatterplot {
252226
252330
  });
252227
252331
  }
252228
252332
  createPointLayer(layerScope, layerCoordination, layerObsPoints) {
252229
- const { theme, delegateHover, targetZ } = this.props;
252230
- const { spatialLayerVisible, spatialLayerOpacity, obsColorEncoding, spatialLayerColor } = layerCoordination;
252231
- const isStaticColor = obsColorEncoding === "spatialLayerColor";
252333
+ const { theme, delegateHover, targetZ, pointMatrixIndices, setTiledPointsLoadingProgress } = this.props;
252334
+ const { spatialLayerVisible, spatialLayerOpacity, obsColorEncoding, spatialLayerColor, featureSelection, featureFilterMode, featureColor } = layerCoordination;
252335
+ const pointFeatureIndex = pointMatrixIndices?.[layerScope]?.featureIndex;
252336
+ let featureIndices = [];
252337
+ if (Array.isArray(featureSelection) && featureSelection.length >= 1) {
252338
+ if (pointFeatureIndex) {
252339
+ featureIndices = featureSelection.map((geneName) => pointFeatureIndex.indexOf(geneName)).filter((i2) => i2 >= 0);
252340
+ }
252341
+ }
252232
252342
  const staticColor = Array.isArray(spatialLayerColor) && spatialLayerColor.length === 3 ? spatialLayerColor : getDefaultColor(theme);
252343
+ const defaultColor = getDefaultColor(theme);
252344
+ let getFillColor = null;
252345
+ const hasFeatureSelection = Array.isArray(featureSelection) && featureSelection.length > 0;
252346
+ const showUnselected = featureFilterMode !== "featureSelection";
252347
+ const hasMultipleFeaturesSelected = Array.isArray(featureIndices) && featureIndices.length > 1;
252348
+ if (obsColorEncoding === "spatialLayerColor") {
252349
+ getFillColor = (object2, { index: index2, data: data2, target: target2 }) => {
252350
+ if (!hasFeatureSelection || featureIndices.includes(data2.src.featureIndices[index2])) {
252351
+ target2[0] = staticColor[0];
252352
+ target2[1] = staticColor[1];
252353
+ target2[2] = staticColor[2];
252354
+ target2[3] = 255;
252355
+ return target2;
252356
+ }
252357
+ if (!showUnselected) {
252358
+ if (hasMultipleFeaturesSelected) {
252359
+ target2[3] = 0;
252360
+ }
252361
+ return target2;
252362
+ }
252363
+ target2[0] = defaultColor[0];
252364
+ target2[1] = defaultColor[1];
252365
+ target2[2] = defaultColor[2];
252366
+ target2[3] = 255;
252367
+ return target2;
252368
+ };
252369
+ } else if (obsColorEncoding === "geneSelection") {
252370
+ getFillColor = (object2, { index: index2, data: data2, target: target2 }) => {
252371
+ if (!hasFeatureSelection) {
252372
+ target2[0] = staticColor[0];
252373
+ target2[1] = staticColor[1];
252374
+ target2[2] = staticColor[2];
252375
+ target2[3] = 255;
252376
+ return target2;
252377
+ }
252378
+ const isSelected = featureIndices.includes(data2.src.featureIndices[index2]);
252379
+ if (isSelected) {
252380
+ const featureName = pointFeatureIndex?.[data2.src.featureIndices[index2]];
252381
+ const featureColorMatch = Array.isArray(featureColor) ? featureColor.find((fc) => fc.name === featureName)?.color : null;
252382
+ if (featureColorMatch) {
252383
+ target2[0] = featureColorMatch[0];
252384
+ target2[1] = featureColorMatch[1];
252385
+ target2[2] = featureColorMatch[2];
252386
+ target2[3] = 255;
252387
+ return target2;
252388
+ }
252389
+ target2[0] = staticColor[0];
252390
+ target2[1] = staticColor[1];
252391
+ target2[2] = staticColor[2];
252392
+ target2[3] = 255;
252393
+ return target2;
252394
+ }
252395
+ if (!showUnselected) {
252396
+ if (hasMultipleFeaturesSelected) {
252397
+ target2[3] = 0;
252398
+ }
252399
+ return target2;
252400
+ }
252401
+ target2[0] = defaultColor[0];
252402
+ target2[1] = defaultColor[1];
252403
+ target2[2] = defaultColor[2];
252404
+ target2[3] = 255;
252405
+ return target2;
252406
+ };
252407
+ } else if (obsColorEncoding === "randomByFeature") {
252408
+ {
252409
+ getFillColor = (object2, { index: index2, data: data2, target: target2 }) => {
252410
+ if (!hasFeatureSelection || featureIndices.includes(data2.src.featureIndices[index2])) {
252411
+ const varIndex = data2.src.featureIndices[index2];
252412
+ const color2 = PALETTE[varIndex % PALETTE.length];
252413
+ target2[0] = color2[0];
252414
+ target2[1] = color2[1];
252415
+ target2[2] = color2[2];
252416
+ target2[3] = 255;
252417
+ return target2;
252418
+ }
252419
+ if (!showUnselected) {
252420
+ if (hasMultipleFeaturesSelected) {
252421
+ target2[3] = 0;
252422
+ }
252423
+ return target2;
252424
+ }
252425
+ target2[0] = defaultColor[0];
252426
+ target2[1] = defaultColor[1];
252427
+ target2[2] = defaultColor[2];
252428
+ target2[3] = 255;
252429
+ return target2;
252430
+ };
252431
+ }
252432
+ } else if (obsColorEncoding === "random") {
252433
+ {
252434
+ getFillColor = (object2, { index: index2, data: data2, target: target2 }) => {
252435
+ if (!hasFeatureSelection || featureIndices.includes(data2.src.featureIndices[index2])) {
252436
+ const color2 = PALETTE[index2 % PALETTE.length];
252437
+ target2[0] = color2[0];
252438
+ target2[1] = color2[1];
252439
+ target2[2] = color2[2];
252440
+ target2[3] = 255;
252441
+ return target2;
252442
+ }
252443
+ if (!showUnselected) {
252444
+ if (hasMultipleFeaturesSelected) {
252445
+ target2[3] = 0;
252446
+ }
252447
+ return target2;
252448
+ }
252449
+ target2[0] = defaultColor[0];
252450
+ target2[1] = defaultColor[1];
252451
+ target2[2] = defaultColor[2];
252452
+ target2[3] = 255;
252453
+ return target2;
252454
+ };
252455
+ }
252456
+ }
252457
+ const isStaticColor = obsColorEncoding === "spatialLayerColor";
252233
252458
  const getMoleculeColor = (object2, { data: data2, index: index2, target: target2 }) => {
252234
252459
  const obsId = data2.src.obsIndex[index2];
252235
252460
  if (data2.src.obsLabelsMap && data2.src.uniqueObsLabels && data2.src.PALETTE) {
@@ -252241,11 +252466,131 @@ class Spatial2 extends AbstractSpatialOrScatterplot {
252241
252466
  }
252242
252467
  return target2;
252243
252468
  };
252244
- const { obsPointsModelMatrix, obsPoints } = this.obsPointsData?.[layerScope]?.src ?? {};
252245
- const hasZ = obsPoints?.shape?.[0] === 3;
252469
+ const { obsPointsModelMatrix, obsPoints, obsPointsTilingType, loadPointsInRect } = this.obsPointsData?.[layerScope]?.src ?? {};
252470
+ obsPoints?.shape?.[0] === 3;
252246
252471
  const modelMatrix2 = obsPointsModelMatrix?.clone();
252247
- if (hasZ && typeof targetZ !== "number") {
252248
- log$b.warn("Spatial: targetZ is not a number, so the point layer will not be filtered by Z.");
252472
+ if (obsPointsTilingType === "tiled") {
252473
+ return new TileLayer({
252474
+ id: `Tiled-${POINT_LAYER_PREFIX}${layerScope}`,
252475
+ coordinateSystem: COORDINATE_SYSTEM.CARTESIAN,
252476
+ // NOTE: picking is not working due to https://github.com/vitessce/vitessce/issues/2039
252477
+ modelMatrix: modelMatrix2,
252478
+ pickable: true,
252479
+ autoHighlight: true,
252480
+ onHover: (info2) => delegateHover(info2, "point", layerScope),
252481
+ opacity: spatialLayerOpacity,
252482
+ visible: spatialLayerVisible,
252483
+ // Since points are tiled but not multi-resolution,
252484
+ // it provides no benefit to request tiles at different zoom levels.
252485
+ // TODO: Should this value be the same for
252486
+ // every dataset, or do we need to
252487
+ // adjust based on the extent/density of the
252488
+ // point data (and/or account for modelMatrix, etc)?
252489
+ maxZoom: -1,
252490
+ minZoom: -1,
252491
+ tileSize: 512,
252492
+ // refinementStrategy: 'no-overlap',
252493
+ getTileData: async (tileInfo) => {
252494
+ const { index: index2, signal, bbox: bbox2, zoom: zoom2 } = tileInfo;
252495
+ const { z: z2, x: x2, y: y2 } = index2;
252496
+ this.obsPointsLoadingStatus = {
252497
+ ...this.obsPointsLoadingStatus,
252498
+ [`${layerScope}-${z2}-${x2}-${y2}`]: "loading"
252499
+ };
252500
+ setTiledPointsLoadingProgress(this.obsPointsLoadingStatus);
252501
+ signal.addEventListener("abort", () => {
252502
+ this.obsPointsLoadingStatus = {
252503
+ ...this.obsPointsLoadingStatus,
252504
+ [`${layerScope}-${z2}-${x2}-${y2}`]: "aborted"
252505
+ };
252506
+ setTiledPointsLoadingProgress(this.obsPointsLoadingStatus);
252507
+ });
252508
+ const pointsInTile = await loadPointsInRect(bbox2, signal);
252509
+ this.obsPointsLoadingStatus = {
252510
+ ...this.obsPointsLoadingStatus,
252511
+ [`${layerScope}-${z2}-${x2}-${y2}`]: "success"
252512
+ };
252513
+ setTiledPointsLoadingProgress(this.obsPointsLoadingStatus);
252514
+ return {
252515
+ src: pointsInTile.data,
252516
+ length: pointsInTile.shape?.[1] || 0
252517
+ };
252518
+ },
252519
+ renderSubLayers: (subLayerProps) => {
252520
+ const { bbox: bbox2, content: tileData } = subLayerProps.tile;
252521
+ const { left: left2, top: top2, right: right2, bottom: bottom2 } = bbox2;
252522
+ const hasFeatureIndicesMinMax = !showUnselected && Array.isArray(featureIndices) && featureIndices.length === 1;
252523
+ const FILTER_PLACEHOLDER = 0;
252524
+ const featureIndicesMinMax = hasFeatureIndicesMinMax ? [featureIndices[0], featureIndices[0]] : [FILTER_PLACEHOLDER, FILTER_PLACEHOLDER];
252525
+ return new ScatterplotLayer(subLayerProps, {
252526
+ bounds: [left2, top2, right2, bottom2],
252527
+ data: tileData,
252528
+ getRadius: 5,
252529
+ radiusMaxPixels: 3,
252530
+ // getPosition: d => d,
252531
+ // getFillColor: [255, 0, 0],
252532
+ getPosition: (object2, { data: data2, index: index2, target: target2 }) => {
252533
+ target2[0] = data2.src.x[index2];
252534
+ target2[1] = data2.src.y[index2];
252535
+ target2[2] = 0;
252536
+ return target2;
252537
+ },
252538
+ getFillColor,
252539
+ // TODO: Is the picking stuff needed here in the Sublayer, or in the parent TileLayer?
252540
+ pickable: true,
252541
+ autoHighlight: true,
252542
+ onHover: (info2) => delegateHover(info2, "point", layerScope),
252543
+ // Use GPU filtering to filter to only the points in the tile bounding box,
252544
+ // since the row groups may contain points from other tiles.
252545
+ // Note: this can be improved using filterCategories,
252546
+ // but it is not available until post-v9 deck.gl/extensions.
252547
+ filterRange: [[left2, right2], [top2, bottom2], featureIndicesMinMax],
252548
+ getFilterValue: hasFeatureIndicesMinMax ? (object2, { data: data2, index: index2 }) => [
252549
+ data2.src.x[index2],
252550
+ data2.src.y[index2],
252551
+ data2.src.featureIndices[index2]
252552
+ ] : (object2, { data: data2, index: index2 }) => [
252553
+ data2.src.x[index2],
252554
+ data2.src.y[index2],
252555
+ FILTER_PLACEHOLDER
252556
+ ],
252557
+ extensions: [
252558
+ new DataFilterExtension({ filterSize: 3 })
252559
+ ],
252560
+ updateTriggers: {
252561
+ getFillColor: [
252562
+ showUnselected,
252563
+ featureColor,
252564
+ obsColorEncoding,
252565
+ spatialLayerColor,
252566
+ featureSelection,
252567
+ hasMultipleFeaturesSelected
252568
+ ],
252569
+ getFilterValue: [hasFeatureIndicesMinMax, showUnselected, featureSelection],
252570
+ filterRange: [hasFeatureIndicesMinMax, showUnselected, featureSelection]
252571
+ }
252572
+ });
252573
+ },
252574
+ updateTriggers: {
252575
+ getTileData: [
252576
+ showUnselected,
252577
+ featureColor,
252578
+ obsColorEncoding,
252579
+ spatialLayerColor,
252580
+ featureSelection,
252581
+ hasMultipleFeaturesSelected
252582
+ ]
252583
+ }
252584
+ /*
252585
+ onTileError: (error) => {
252586
+
252587
+ },
252588
+ onViewportLoad: (loadedTiles) => {
252589
+ // Called when all tiles in the current viewport are loaded.
252590
+ // An array of loaded Tile instances are passed as argument to this function.
252591
+ },
252592
+ */
252593
+ });
252249
252594
  }
252250
252595
  return new ScatterplotLayer({
252251
252596
  id: `${POINT_LAYER_PREFIX}${layerScope}`,
@@ -252272,16 +252617,7 @@ class Spatial2 extends AbstractSpatialOrScatterplot {
252272
252617
  getFillColor: [obsColorEncoding, staticColor],
252273
252618
  getLineColor: [obsColorEncoding, staticColor]
252274
252619
  },
252275
- ...hasZ && typeof targetZ === "number" ? {
252276
- // TODO: support targetT filtering as well.
252277
- // TODO: allow filtering by Z coordinate (rather than slice index)
252278
- // Reference: https://github.com/vitessce/vitessce/issues/2194
252279
- filterRange: [targetZ, targetZ],
252280
- getFilterValue: (object2, { data: data2, index: index2 }) => data2.src.obsPoints.data[2][index2],
252281
- extensions: [
252282
- new DataFilterExtension({ filterSize: 1 })
252283
- ]
252284
- } : {}
252620
+ ...{}
252285
252621
  });
252286
252622
  }
252287
252623
  createSelectionLayer() {
@@ -252821,28 +253157,45 @@ class Spatial2 extends AbstractSpatialOrScatterplot {
252821
253157
  }
252822
253158
  onUpdatePointsData(layerScope) {
252823
253159
  const { obsPoints, pointMultiObsLabels } = this.props;
252824
- const { obsIndex, obsPoints: layerObsPoints, obsPointsModelMatrix } = obsPoints?.[layerScope] || {};
253160
+ const { obsIndex, obsPoints: layerObsPoints, obsPointsModelMatrix, loadPointsInRect, obsPointsTilingType } = obsPoints?.[layerScope] || {};
252825
253161
  const { obsIndex: obsLabelsIndex, obsLabels } = pointMultiObsLabels?.[layerScope] || {};
252826
- if (layerObsPoints) {
252827
- const getCellCoords = makeDefaultGetObsCoords(layerObsPoints);
252828
- this.obsPointsQuadTree[layerScope] = createQuadTree(layerObsPoints, getCellCoords);
253162
+ if (obsPointsTilingType === "tiled") {
252829
253163
  this.obsPointsData[layerScope] = {
252830
253164
  src: {
252831
- obsIndex,
252832
- obsPoints: layerObsPoints,
253165
+ obsPointsTilingType,
253166
+ obsIndex: null,
253167
+ obsPoints: null,
252833
253168
  obsPointsModelMatrix,
252834
253169
  obsLabelsMap: null,
252835
253170
  uniqueObsLabels: null,
252836
- PALETTE: null
253171
+ PALETTE: null,
253172
+ loadPointsInRect
252837
253173
  },
252838
- length: layerObsPoints.shape[1]
253174
+ length: null
252839
253175
  };
252840
- if (obsLabels) {
252841
- const obsLabelsMap = new Map(obsLabelsIndex.map((key2, i2) => [key2, obsLabels[i2]]));
252842
- const uniqueObsLabels = Array.from(new Set(obsLabels));
252843
- this.obsPointsData[layerScope].src.obsLabelsMap = obsLabelsMap;
252844
- this.obsPointsData[layerScope].src.uniqueObsLabels = uniqueObsLabels;
252845
- this.obsPointsData[layerScope].src.PALETTE = PALETTE;
253176
+ } else {
253177
+ if (layerObsPoints) {
253178
+ const getCellCoords = makeDefaultGetObsCoords(layerObsPoints);
253179
+ this.obsPointsQuadTree[layerScope] = createQuadTree(layerObsPoints, getCellCoords);
253180
+ this.obsPointsData[layerScope] = {
253181
+ src: {
253182
+ obsPointsTilingType,
253183
+ obsIndex,
253184
+ obsPoints: layerObsPoints,
253185
+ obsPointsModelMatrix,
253186
+ obsLabelsMap: null,
253187
+ uniqueObsLabels: null,
253188
+ PALETTE: null
253189
+ },
253190
+ length: layerObsPoints.shape[1]
253191
+ };
253192
+ if (obsLabels) {
253193
+ const obsLabelsMap = new Map(obsLabelsIndex.map((key2, i2) => [key2, obsLabels[i2]]));
253194
+ const uniqueObsLabels = Array.from(new Set(obsLabels));
253195
+ this.obsPointsData[layerScope].src.obsLabelsMap = obsLabelsMap;
253196
+ this.obsPointsData[layerScope].src.uniqueObsLabels = uniqueObsLabels;
253197
+ this.obsPointsData[layerScope].src.PALETTE = PALETTE;
253198
+ }
252846
253199
  }
252847
253200
  }
252848
253201
  }
@@ -253051,6 +253404,7 @@ class Spatial2 extends AbstractSpatialOrScatterplot {
253051
253404
  // Data props.
253052
253405
  "obsPoints",
253053
253406
  "pointMultiObsLabels",
253407
+ "pointMatrixIndices",
253054
253408
  // Coordination props.
253055
253409
  "pointLayerScopes",
253056
253410
  "pointLayerCoordination"
@@ -253205,12 +253559,12 @@ class ErrorBoundary extends React__default.Component {
253205
253559
  }
253206
253560
  }
253207
253561
  const LazySpatialThree = React__default.lazy(async () => {
253208
- const { SpatialWrapper: SpatialWrapper2 } = await import("./index-DZg7IgTe.js");
253562
+ const { SpatialWrapper: SpatialWrapper2 } = await import("./index-SH4i0VjQ.js");
253209
253563
  return { default: SpatialWrapper2 };
253210
253564
  });
253211
253565
  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 }) }) }) }));
253212
253566
  const LazySpatialAccelerated = React__default.lazy(async () => {
253213
- const { SpatialWrapper: SpatialWrapper2 } = await import("./index-eU9RLuKQ.js");
253567
+ const { SpatialWrapper: SpatialWrapper2 } = await import("./index-gdPd0Ws9.js");
253214
253568
  return { default: SpatialWrapper2 };
253215
253569
  });
253216
253570
  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 }) }) }) }));
@@ -253325,12 +253679,22 @@ const DEFAULT_VIEW_STATE = {
253325
253679
  const SET_VIEW_STATE_NOOP = () => {
253326
253680
  };
253327
253681
  function getHoverData(hoverInfo, layerType) {
253328
- const { coordinate, sourceLayer: layer, tile } = hoverInfo;
253329
- if (layerType === "segmentation-bitmask" || layerType === "image") {
253682
+ const { coordinate, sourceLayer: layer, tile, index: pointIndex } = hoverInfo;
253683
+ if (layerType === "segmentation-bitmask" || layerType === "image" || layerType === "point") {
253330
253684
  if (coordinate && layer) {
253331
253685
  if (layer.id.startsWith("Tiled") && tile) {
253332
253686
  const { content: content2, bbox: bbox2, index: { z: z2 } } = tile;
253333
253687
  if (content2) {
253688
+ if (layerType === "point" && pointIndex >= 0) {
253689
+ const { src } = content2 || {};
253690
+ const { x: x2, y: y2, featureIndices } = src || {};
253691
+ return {
253692
+ pointIndex,
253693
+ x: x2?.[pointIndex],
253694
+ y: y2?.[pointIndex],
253695
+ featureIndex: featureIndices?.[pointIndex]
253696
+ };
253697
+ }
253334
253698
  const { data: data2, width: width2, height: height2 } = content2;
253335
253699
  const { left: left2, right: right2, top: top2, bottom: bottom2 } = bbox2;
253336
253700
  const bounds2 = [
@@ -253508,6 +253872,8 @@ function SpatialSubscriber(props) {
253508
253872
  CoordinationType$1.SPATIAL_LAYER_VISIBLE,
253509
253873
  CoordinationType$1.SPATIAL_LAYER_OPACITY,
253510
253874
  CoordinationType$1.OBS_COLOR_ENCODING,
253875
+ CoordinationType$1.FEATURE_COLOR,
253876
+ CoordinationType$1.FEATURE_FILTER_MODE,
253511
253877
  CoordinationType$1.FEATURE_SELECTION,
253512
253878
  CoordinationType$1.FEATURE_VALUE_COLORMAP,
253513
253879
  CoordinationType$1.FEATURE_VALUE_COLORMAP_RANGE,
@@ -253518,7 +253884,10 @@ function SpatialSubscriber(props) {
253518
253884
  CoordinationType$1.LEGEND_VISIBLE
253519
253885
  ], coordinationScopes, coordinationScopesBy, CoordinationType$1.POINT_LAYER);
253520
253886
  const [volumeLoadingStatus, setVolumeLoadingStatus] = useState(null);
253521
- const [{ volumeLoadingProgress }, { setVolumeLoadingProgress }] = useAuxiliaryCoordination(["spatialAcceleratedVolumeLoadingProgress"], coordinationScopes);
253887
+ const [{ volumeLoadingProgress, tiledPointsLoadingProgress }, { setVolumeLoadingProgress, setTiledPointsLoadingProgress }] = useAuxiliaryCoordination([
253888
+ "spatialAcceleratedVolumeLoadingProgress",
253889
+ "spatialTiledPointsLoadingProgress"
253890
+ ], coordinationScopes);
253522
253891
  useEffect(() => {
253523
253892
  if (volumeLoadingStatus && volumeLoadingStatus.loadingProgress) {
253524
253893
  setVolumeLoadingProgress(volumeLoadingStatus);
@@ -253535,6 +253904,7 @@ function SpatialSubscriber(props) {
253535
253904
  const height2 = threeFor3d && deckHeight === void 0 ? threeHeight : deckHeight;
253536
253905
  const [obsPointsData, obsPointsDataStatus, obsPointsUrls, obsPointsErrors] = useMultiObsPoints(coordinationScopes, coordinationScopesBy, loaders, dataset, mergeCoordination, uuid2);
253537
253906
  const [pointMultiObsLabelsData, pointMultiObsLabelsDataStatus, pointMultiObsLabelsErrors] = usePointMultiObsLabels(coordinationScopes, coordinationScopesBy, loaders, dataset);
253907
+ const [pointMultiIndicesData, pointMultiIndicesDataStatus, pointMultiIndicesDataErrors] = usePointMultiObsFeatureMatrixIndices(coordinationScopes, coordinationScopesBy, loaders, dataset);
253538
253908
  const [obsSpotsData, obsSpotsDataStatus, obsSpotsUrls, obsSpotsErrors] = useMultiObsSpots(coordinationScopes, coordinationScopesBy, loaders, dataset, mergeCoordination, uuid2);
253539
253909
  const [obsSpotsSetsData, obsSpotsSetsDataStatus, obsSpotSetsUrls, obsSpotsSetsErrors] = useSpotMultiObsSets(coordinationScopes, coordinationScopesBy, loaders, dataset);
253540
253910
  const [obsSpotsFeatureLabelsData, obsSpotsFeatureLabelsDataStatus, obsSpotsFeatureLabelsUrls, obsSpotsFeatureLabelsErrors] = useSpotMultiFeatureLabels(coordinationScopes, coordinationScopesBy, loaders, dataset);
@@ -253568,6 +253938,7 @@ function SpatialSubscriber(props) {
253568
253938
  ...spotMultiFeatureSelectionErrors,
253569
253939
  ...spotMultiIndicesDataErrors,
253570
253940
  ...pointMultiObsLabelsErrors,
253941
+ ...pointMultiIndicesDataErrors,
253571
253942
  ...segmentationMultiFeatureSelectionErrors,
253572
253943
  ...segmentationMultiIndicesDataErrors,
253573
253944
  ...obsSegmentationsLocationsDataErrors
@@ -253587,6 +253958,7 @@ function SpatialSubscriber(props) {
253587
253958
  // Points
253588
253959
  obsPointsDataStatus,
253589
253960
  pointMultiObsLabelsDataStatus,
253961
+ pointMultiIndicesDataStatus,
253590
253962
  // Segmentations
253591
253963
  obsSegmentationsDataStatus,
253592
253964
  obsSegmentationsSetsDataStatus,
@@ -253774,14 +254146,19 @@ function SpatialSubscriber(props) {
253774
254146
  pointLayerScopes?.forEach((pointLayerScope) => {
253775
254147
  const { setObsHighlight } = pointLayerCoordination?.[1]?.[pointLayerScope] || {};
253776
254148
  if (hoverData && layerType === "point" && layerScope === pointLayerScope) {
253777
- const obsI = hoverData;
253778
- const { obsIndex } = obsPointsData?.[pointLayerScope] || {};
253779
- const obsId = obsIndex?.[obsI];
253780
- if (obsIndex && obsId) {
254149
+ if (typeof hoverData === "object" && hoverData?.pointIndex) {
253781
254150
  showAnyTooltip = true;
253782
- setObsHighlight(obsId);
254151
+ setObsHighlight(hoverData.pointIndex);
253783
254152
  } else {
253784
- setObsHighlight(null);
254153
+ const obsI = hoverData;
254154
+ const { obsIndex } = obsPointsData?.[pointLayerScope] || {};
254155
+ const obsId = obsIndex?.[obsI];
254156
+ if (obsIndex && obsId) {
254157
+ showAnyTooltip = true;
254158
+ setObsHighlight(obsId);
254159
+ } else {
254160
+ setObsHighlight(null);
254161
+ }
253785
254162
  }
253786
254163
  } else {
253787
254164
  setObsHighlight(null);
@@ -253823,7 +254200,7 @@ function SpatialSubscriber(props) {
253823
254200
  target: [targetX2, targetY2, targetZ],
253824
254201
  rotationX,
253825
254202
  rotationOrbit
253826
- } : 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, {
254203
+ } : 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, {
253827
254204
  parentUuid: uuid2,
253828
254205
  width: width2,
253829
254206
  height: height2,
@@ -253864,7 +254241,8 @@ function SpatialSubscriber(props) {
253864
254241
  spotMultiFeatureLabels: obsSpotsFeatureLabelsData,
253865
254242
  // Points
253866
254243
  pointLayerScopes,
253867
- pointLayerCoordination
254244
+ pointLayerCoordination,
254245
+ pointMultiIndicesData
253868
254246
  }
253869
254247
  ), jsxRuntimeExports.jsx(
253870
254248
  ChannelNamesLegend,
@@ -262826,17 +263204,35 @@ function SpotLayerController(props) {
262826
263204
  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 }) })] }) }) });
262827
263205
  }
262828
263206
  const useStyles$e = makeStyles()(() => ({
263207
+ pointLayerButton: {
263208
+ borderStyle: "dashed",
263209
+ marginTop: "10px",
263210
+ marginBottom: "10px",
263211
+ fontWeight: 400
263212
+ },
263213
+ pointFeatureControllerGrid: {
263214
+ padding: "0",
263215
+ flexWrap: "nowrap"
263216
+ },
263217
+ pointFeatureExpansionButton: {
263218
+ display: "inline-block",
263219
+ margin: 0,
263220
+ padding: 0,
263221
+ minWidth: 0,
263222
+ lineHeight: 1,
263223
+ width: "50%"
263224
+ },
262829
263225
  layerTypePointIcon: {
262830
263226
  height: "100%",
262831
- marginLeft: "1px",
263227
+ paddingLeft: "2px",
262832
263228
  fill: "currentColor",
262833
- fontSize: "20px",
263229
+ fontSize: "14px",
262834
263230
  width: "50%",
262835
- maxWidth: "20px"
263231
+ maxWidth: "16px"
262836
263232
  }
262837
263233
  }));
262838
263234
  function PointLayerEllipsisMenu(props) {
262839
- const { featureSelection, obsColorEncoding, setObsColorEncoding, featureValueColormapRange, setFeatureValueColormapRange, tooltipsVisible, setTooltipsVisible, tooltipCrosshairsVisible, setTooltipCrosshairsVisible, legendVisible, setLegendVisible } = props;
263235
+ const { featureSelection, featureFilterMode, setFeatureFilterMode, obsColorEncoding, setObsColorEncoding, featureValueColormapRange, setFeatureValueColormapRange, tooltipsVisible, setTooltipsVisible, tooltipCrosshairsVisible, setTooltipCrosshairsVisible, legendVisible, setLegendVisible } = props;
262840
263236
  const [open2, setOpen] = useState(false);
262841
263237
  const { classes: selectClasses2 } = useSelectStyles();
262842
263238
  const { classes: menuClasses } = useEllipsisMenuStyles();
@@ -262845,17 +263241,84 @@ function PointLayerEllipsisMenu(props) {
262845
263241
  const tooltipsVisibleId = $bdb11010cef70236$export$f680877a34711e37();
262846
263242
  const crosshairsVisibleId = $bdb11010cef70236$export$f680877a34711e37();
262847
263243
  const legendVisibleId = $bdb11010cef70236$export$f680877a34711e37();
262848
- 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" } } })] })] });
263244
+ const featureFilterModeId = $bdb11010cef70236$export$f680877a34711e37();
263245
+ 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" } } })] })] });
262849
263246
  }
262850
263247
  function PointLayerController(props) {
262851
- const { theme, layerScope, layerCoordination, setLayerCoordination, palette = null } = props;
262852
- const { obsType, spatialLayerVisible: visible, spatialLayerOpacity: opacity2, obsColorEncoding, featureSelection, featureValueColormap, featureValueColormapRange, spatialLayerColor: color2, tooltipsVisible, tooltipCrosshairsVisible, legendVisible } = layerCoordination;
262853
- const { setSpatialLayerVisible: setVisible, setSpatialLayerOpacity: setOpacity2, setObsColorEncoding, setFeatureSelection, setFeatureValueColormap, setFeatureValueColormapRange, setSpatialLayerColor: setColor2, setTooltipsVisible, setTooltipCrosshairsVisible, setLegendVisible } = setLayerCoordination;
263248
+ const { theme, layerScope, layerCoordination, setLayerCoordination, palette = null, pointMatrixIndicesData, tiledPointsLoadingProgress } = props;
263249
+ const [open2, setOpen] = useState(false);
263250
+ const loadingDoneFraction = useMemo$1(() => {
263251
+ if (tiledPointsLoadingProgress && typeof tiledPointsLoadingProgress === "object") {
263252
+ return 1 - Object.values(tiledPointsLoadingProgress).filter((status) => status === "loading").length / Object.values(tiledPointsLoadingProgress).length;
263253
+ }
263254
+ return 1;
263255
+ }, [tiledPointsLoadingProgress]);
263256
+ const { obsType, spatialLayerVisible: visible, spatialLayerOpacity: opacity2, obsColorEncoding, featureColor, featureFilterMode, featureSelection, featureValueColormap, featureValueColormapRange, spatialLayerColor, tooltipsVisible, tooltipCrosshairsVisible, legendVisible } = layerCoordination;
263257
+ const { setSpatialLayerVisible: setVisible, setSpatialLayerOpacity: setOpacity2, setObsColorEncoding, setFeatureColor, setFeatureFilterMode, setFeatureSelection, setFeatureValueColormap, setFeatureValueColormapRange, setSpatialLayerColor, setTooltipsVisible, setTooltipCrosshairsVisible, setLegendVisible } = setLayerCoordination;
262854
263258
  const label2 = capitalize$3(obsType);
262855
263259
  const visibleSetting = typeof visible === "boolean" ? visible : true;
262856
263260
  const Visibility = useMemo$1(() => visibleSetting ? VisibilityIcon : VisibilityOffIcon, [visibleSetting]);
262857
- const isStaticColor = obsColorEncoding === "spatialLayerColor";
262858
- const isColormap = obsColorEncoding === "geneSelection";
263261
+ const hasUnspecifiedFeatureColors = useMemo$1(() => {
263262
+ if (Array.isArray(featureSelection)) {
263263
+ if (Array.isArray(featureColor)) {
263264
+ return featureSelection.some((featureName) => {
263265
+ const colorForFeature = featureColor.find((fc) => fc.name === featureName);
263266
+ return !colorForFeature;
263267
+ });
263268
+ }
263269
+ return featureSelection.length > 0;
263270
+ }
263271
+ return true;
263272
+ }, [featureColor, featureSelection]);
263273
+ const isStaticColor = obsColorEncoding === "spatialLayerColor" || obsColorEncoding === "geneSelection";
263274
+ const showStaticColor = obsColorEncoding === "spatialLayerColor" || obsColorEncoding === "geneSelection" && hasUnspecifiedFeatureColors;
263275
+ const isColormap = false;
263276
+ const hasSingleSelectedFeature = obsColorEncoding === "geneSelection" && Array.isArray(featureSelection) && featureSelection.length === 1;
263277
+ const color2 = useMemo$1(() => {
263278
+ if (showStaticColor) {
263279
+ return spatialLayerColor;
263280
+ }
263281
+ if (hasSingleSelectedFeature) {
263282
+ const selectedFeatureColor = featureColor?.find((fc) => fc.name === featureSelection[0])?.color;
263283
+ if (selectedFeatureColor) {
263284
+ return selectedFeatureColor;
263285
+ }
263286
+ }
263287
+ return null;
263288
+ }, [
263289
+ hasSingleSelectedFeature,
263290
+ spatialLayerColor,
263291
+ featureColor,
263292
+ featureSelection,
263293
+ showStaticColor
263294
+ ]);
263295
+ const setColor2 = useCallback((newColor) => {
263296
+ if (showStaticColor) {
263297
+ setSpatialLayerColor(newColor);
263298
+ } else if (hasSingleSelectedFeature) {
263299
+ const featureColorIndex = featureColor?.findIndex((fc) => fc.name === featureSelection[0]);
263300
+ if (featureColorIndex !== void 0 && featureColorIndex >= 0) {
263301
+ const newFeatureColor = [...featureColor];
263302
+ newFeatureColor[featureColorIndex] = {
263303
+ name: featureSelection[0],
263304
+ color: newColor
263305
+ };
263306
+ setFeatureColor(newFeatureColor);
263307
+ } else {
263308
+ setFeatureColor([
263309
+ ...featureColor,
263310
+ { name: featureSelection[0], color: newColor }
263311
+ ]);
263312
+ }
263313
+ }
263314
+ }, [
263315
+ hasSingleSelectedFeature,
263316
+ setSpatialLayerColor,
263317
+ featureColor,
263318
+ setFeatureColor,
263319
+ featureSelection,
263320
+ showStaticColor
263321
+ ]);
262859
263322
  const { classes: classes2 } = useStyles$e();
262860
263323
  const { classes: lcClasses } = useControllerSectionStyles();
262861
263324
  const { classes: menuClasses } = useEllipsisMenuStyles();
@@ -262864,7 +263327,10 @@ function PointLayerController(props) {
262864
263327
  setVisible(nextVisible);
262865
263328
  }, [visible, setVisible]);
262866
263329
  const handleOpacityChange = useCallback((e3, v) => setOpacity2(v), [setOpacity2]);
262867
- 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 }) })] }) }) });
263330
+ useCallback(() => setOpen((prev2) => !prev2), []);
263331
+ const [coloringTabIndex, setColoringTabIndex] = useState(0);
263332
+ const { featureIndex } = pointMatrixIndicesData || {};
263333
+ 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] }) });
262868
263334
  }
262869
263335
  const useStyles$d = makeStyles()(() => ({
262870
263336
  layerTypeSegmentationIcon: {
@@ -264395,7 +264861,7 @@ function ImageLayerEllipsisMenu(props) {
264395
264861
  const { classes: selectClasses2 } = useSelectStyles();
264396
264862
  const { classes: menuClasses } = useEllipsisMenuStyles();
264397
264863
  const [localLodSliderValue, setLocalLodSliderValue] = useState(convertLodFactorToLogarithmicSliderValue(spatialLodFactor || 1));
264398
- React__default.useEffect(() => {
264864
+ useEffect(() => {
264399
264865
  setLocalLodSliderValue(convertLodFactorToLogarithmicSliderValue(spatialLodFactor || 1));
264400
264866
  }, [spatialLodFactor]);
264401
264867
  const is3dMode = spatialRenderingMode === "3D";
@@ -264531,7 +264997,7 @@ function GlobalDimensionSlider(props) {
264531
264997
  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 })] }) }) });
264532
264998
  }
264533
264999
  function LayerController(props) {
264534
- const { theme, coordinationScopesRaw, segmentationLayerScopes, segmentationLayerCoordination, segmentationChannelScopesByLayer, segmentationChannelCoordination, images, imageLayerScopes, imageLayerCoordination, targetT, targetZ, setTargetT, setTargetZ, spatialRenderingMode, setSpatialRenderingMode, imageChannelScopesByLayer, imageChannelCoordination, spotLayerScopes, spotLayerCoordination, pointLayerScopes, pointLayerCoordination, volumeLoadingStatus } = props;
265000
+ 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;
264535
265001
  const anyLayerHasT = Object.values(images || {}).some((image2) => image2?.image?.instance.hasTStack());
264536
265002
  const anyLayerHasZ = Object.values(images || {}).some((image2) => image2?.image?.instance.hasZStack());
264537
265003
  const maxT = Object.values(images || {}).reduce((a2, h2) => Math.max(a2, h2?.image?.instance.getNumT()), 1) - 1;
@@ -264540,7 +265006,7 @@ function LayerController(props) {
264540
265006
  const reversedSegmentationLayerScopes = useMemo$1(() => [...segmentationLayerScopes || []].reverse(), [segmentationLayerScopes]);
264541
265007
  const reversedSpotLayerScopes = useMemo$1(() => [...spotLayerScopes || []].reverse(), [spotLayerScopes]);
264542
265008
  const reversedPointLayerScopes = useMemo$1(() => [...pointLayerScopes || []].reverse(), [pointLayerScopes]);
264543
- 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))] });
265009
+ 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))] });
264544
265010
  }
264545
265011
  function LayerControllerSubscriber(props) {
264546
265012
  const { coordinationScopes: coordinationScopesRaw, coordinationScopesBy: coordinationScopesByRaw, closeButtonVisible, downloadButtonVisible, removeGridComponent, theme, title: title2 = "Spatial Layers", uuid: uuid2 } = props;
@@ -264624,6 +265090,8 @@ function LayerControllerSubscriber(props) {
264624
265090
  CoordinationType$1.SPATIAL_LAYER_OPACITY,
264625
265091
  CoordinationType$1.SPATIAL_SPOT_RADIUS,
264626
265092
  CoordinationType$1.OBS_COLOR_ENCODING,
265093
+ CoordinationType$1.FEATURE_COLOR,
265094
+ CoordinationType$1.FEATURE_FILTER_MODE,
264627
265095
  CoordinationType$1.FEATURE_SELECTION,
264628
265096
  CoordinationType$1.FEATURE_VALUE_COLORMAP,
264629
265097
  CoordinationType$1.FEATURE_VALUE_COLORMAP_RANGE,
@@ -264632,7 +265100,10 @@ function LayerControllerSubscriber(props) {
264632
265100
  CoordinationType$1.TOOLTIP_CROSSHAIRS_VISIBLE,
264633
265101
  CoordinationType$1.LEGEND_VISIBLE
264634
265102
  ], coordinationScopes, coordinationScopesBy, CoordinationType$1.POINT_LAYER);
264635
- const [{ volumeLoadingProgress: volumeLoadingStatus }] = useAuxiliaryCoordination(["spatialAcceleratedVolumeLoadingProgress"], coordinationScopes);
265103
+ const [{ volumeLoadingProgress: volumeLoadingStatus, tiledPointsLoadingProgress }] = useAuxiliaryCoordination([
265104
+ "spatialAcceleratedVolumeLoadingProgress",
265105
+ "spatialTiledPointsLoadingProgress"
265106
+ ], coordinationScopes);
264636
265107
  const [spatialLayout] = useComponentLayout("spatial", ["spatialImageLayer"], coordinationScopes);
264637
265108
  const layerControllerRef = useRef();
264638
265109
  const [componentWidth, componentHeight] = useClosestVitessceContainerSize(layerControllerRef);
@@ -264641,19 +265112,22 @@ function LayerControllerSubscriber(props) {
264641
265112
  const [imageData, imageDataStatus, imageUrls, imageErrors] = useMultiImages(coordinationScopes, coordinationScopesBy, loaders, dataset, mergeCoordination, uuid2);
264642
265113
  const [obsSpotsData, obsSpotsDataStatus, obsSpotsUrls, obsSpotsErrors] = useMultiObsSpots(coordinationScopes, coordinationScopesBy, loaders, dataset, mergeCoordination, uuid2);
264643
265114
  const [obsPointsData, obsPointsDataStatus, obsPointsUrls, obsPointsErrors] = useMultiObsPoints(coordinationScopes, coordinationScopesBy, loaders, dataset, mergeCoordination, uuid2);
265115
+ const [pointMultiIndicesData, pointMultiIndicesDataStatus, pointMultiIndicesDataErrors] = usePointMultiObsFeatureMatrixIndices(coordinationScopes, coordinationScopesBy, loaders, dataset);
264644
265116
  const errors = [
264645
265117
  ...obsSegmentationsErrors,
264646
265118
  ...imageErrors,
264647
265119
  ...obsSpotsErrors,
264648
- ...obsPointsErrors
265120
+ ...obsPointsErrors,
265121
+ ...pointMultiIndicesDataErrors
264649
265122
  ];
264650
265123
  const isReady = useReady([
264651
265124
  obsSpotsDataStatus,
264652
265125
  obsPointsDataStatus,
264653
265126
  obsSegmentationsDataStatus,
264654
- imageDataStatus
265127
+ imageDataStatus,
265128
+ pointMultiIndicesDataStatus
264655
265129
  ]);
264656
- 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 }) });
265130
+ 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 }) });
264657
265131
  }
264658
265132
  const isIterable$2 = (obj) => Symbol.iterator in obj;
264659
265133
  const hasIterableEntries = (value2) => (
@@ -266568,7 +267042,7 @@ function HiglassGlobalStyles(props) {
266568
267042
  }
266569
267043
  register({ dataFetcher: ZarrMultivecDataFetcher_default, config: ZarrMultivecDataFetcher_default.config }, { pluginType: "dataFetcher" });
266570
267044
  const LazyHiGlassComponent = React__default.lazy(async () => {
266571
- const { HiGlassComponent } = await import("./higlass-CtfTtjGe.js");
267045
+ const { HiGlassComponent } = await import("./higlass-Chx9S58D.js");
266572
267046
  return { default: HiGlassComponent };
266573
267047
  });
266574
267048
  const HG_SIZE = 800;
@@ -269515,7 +269989,7 @@ function NeuroglancerGlobalStyles(props) {
269515
269989
  const { classes: classes2 } = props;
269516
269990
  return jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx(GlobalStyles$3, { styles: globalNeuroglancerCss }), jsxRuntimeExports.jsx(ScopedGlobalStyles, { styles: globalNeuroglancerStyles, parentClassName: classes2.neuroglancerWrapper })] });
269517
269991
  }
269518
- const LazyReactNeuroglancer = React__default.lazy(() => import("./ReactNeuroglancer-D9Izf3YW.js"));
269992
+ const LazyReactNeuroglancer = React__default.lazy(() => import("./ReactNeuroglancer-rd4zjvRk.js"));
269519
269993
  function createWorker() {
269520
269994
  return new WorkerFactory();
269521
269995
  }
@@ -290689,7 +291163,7 @@ function cureLocalIntersections(start2, triangles, dim) {
290689
291163
  let p = start2;
290690
291164
  do {
290691
291165
  const a2 = p.prev, b2 = p.next.next;
290692
- if (!equals$1(a2, b2) && intersects$2(a2, p, p.next, b2) && locallyInside(a2, b2) && locallyInside(b2, a2)) {
291166
+ if (!equals$1(a2, b2) && intersects$3(a2, p, p.next, b2) && locallyInside(a2, b2) && locallyInside(b2, a2)) {
290693
291167
  triangles.push(a2.i / dim | 0);
290694
291168
  triangles.push(p.i / dim | 0);
290695
291169
  triangles.push(b2.i / dim | 0);
@@ -290867,7 +291341,7 @@ function area$3(p, q, r3) {
290867
291341
  function equals$1(p1, p2) {
290868
291342
  return p1.x === p2.x && p1.y === p2.y;
290869
291343
  }
290870
- function intersects$2(p1, q1, p2, q2) {
291344
+ function intersects$3(p1, q1, p2, q2) {
290871
291345
  const o1 = sign$2(area$3(p1, q1, p2));
290872
291346
  const o2 = sign$2(area$3(p1, q1, q2));
290873
291347
  const o3 = sign$2(area$3(p2, q2, p1));
@@ -290888,7 +291362,7 @@ function sign$2(num) {
290888
291362
  function intersectsPolygon(a2, b2) {
290889
291363
  let p = a2;
290890
291364
  do {
290891
- 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;
291365
+ 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;
290892
291366
  p = p.next;
290893
291367
  } while (p !== a2);
290894
291368
  return false;
@@ -319899,7 +320373,7 @@ function place(b2, a2, c2) {
319899
320373
  c2.y = a2.y;
319900
320374
  }
319901
320375
  }
319902
- function intersects$1(a2, b2) {
320376
+ function intersects$2(a2, b2) {
319903
320377
  var dr = a2.r + b2.r - 1e-6, dx = b2.x - a2.x, dy = b2.y - a2.y;
319904
320378
  return dr > 0 && dr * dr > dx * dx + dy * dy;
319905
320379
  }
@@ -319929,13 +320403,13 @@ function packSiblingsRandom(circles, random2) {
319929
320403
  j = b2.next, k = a2.previous, sj = b2._.r, sk = a2._.r;
319930
320404
  do {
319931
320405
  if (sj <= sk) {
319932
- if (intersects$1(j._, c2._)) {
320406
+ if (intersects$2(j._, c2._)) {
319933
320407
  b2 = j, a2.next = b2, b2.previous = a2, --i2;
319934
320408
  continue pack;
319935
320409
  }
319936
320410
  sj += j._.r, j = j.next;
319937
320411
  } else {
319938
- if (intersects$1(k._, c2._)) {
320412
+ if (intersects$2(k._, c2._)) {
319939
320413
  a2 = k, a2.next = b2, b2.previous = a2, --i2;
319940
320414
  continue pack;
319941
320415
  }
@@ -354680,7 +355154,7 @@ class ObsPointsCsvLoader extends CsvLoader {
354680
355154
  data: [obsLocationsX, obsLocationsY],
354681
355155
  shape: [2, obsLocationsX.length]
354682
355156
  };
354683
- this.cachedResult = { obsIndex, obsPoints };
355157
+ this.cachedResult = { obsIndex, obsPoints, obsPointsTilingType: "full" };
354684
355158
  return this.cachedResult;
354685
355159
  }
354686
355160
  async load() {
@@ -356312,7 +356786,7 @@ class ObsPointsAnndataLoader extends AbstractTwoStepLoader {
356312
356786
  this.dataSource.loadObsIndex(path2),
356313
356787
  this.loadPoints()
356314
356788
  ]);
356315
- return new LoaderResult({ obsIndex, obsPoints }, null, coordinationValues);
356789
+ return new LoaderResult({ obsIndex, obsPoints, obsPointsTilingType: "full" }, null, coordinationValues);
356316
356790
  }
356317
356791
  }
356318
356792
  class ObsLocationsAnndataLoader extends AbstractTwoStepLoader {
@@ -357929,6 +358403,17 @@ class SpatialDataObsPointsLoader extends AbstractTwoStepLoader {
357929
358403
  this.locations = locations;
357930
358404
  return this.locations;
357931
358405
  }
358406
+ async loadPointsInRect(bounds2, signal) {
358407
+ const { path: path2 } = this.options;
358408
+ let locations;
358409
+ const formatVersion = await this.dataSource.getPointsFormatVersion(path2);
358410
+ if (formatVersion === "0.1") {
358411
+ locations = await this.dataSource.loadPointsInRect(path2, bounds2, signal);
358412
+ } else {
358413
+ throw new UnknownSpatialDataFormatError("Only points format version 0.1 is supported.");
358414
+ }
358415
+ return locations;
358416
+ }
357932
358417
  async loadObsIndex() {
357933
358418
  const { tablePath, path: path2 } = this.options;
357934
358419
  if (tablePath) {
@@ -357941,11 +358426,26 @@ class SpatialDataObsPointsLoader extends AbstractTwoStepLoader {
357941
358426
  }
357942
358427
  return null;
357943
358428
  }
358429
+ async supportsTiling() {
358430
+ const { path: path2 } = this.options;
358431
+ const [formatVersion, zattrs, hasRequiredColumnsAndRowGroupSize] = await Promise.all([
358432
+ // Check the points format version.
358433
+ this.dataSource.getPointsFormatVersion(path2),
358434
+ // Check for the presence of bounding_box metadata.
358435
+ this.dataSource.loadSpatialDataElementAttrs(path2),
358436
+ // Check the size of parquet row groups,
358437
+ // and for the presence of morton_code_2d and feature_index columns.
358438
+ this.dataSource.supportsLoadPointsInRect(path2)
358439
+ ]);
358440
+ const isSupportedVersion = formatVersion === "0.1";
358441
+ const boundingBox = zattrs?.bounding_box;
358442
+ const hasBoundingBox2D = typeof boundingBox?.x_max === "number" && typeof boundingBox?.x_min === "number" && typeof boundingBox?.y_max === "number" && typeof boundingBox?.y_min === "number";
358443
+ return isSupportedVersion && hasBoundingBox2D && hasRequiredColumnsAndRowGroupSize;
358444
+ }
357944
358445
  async load() {
357945
- const [obsIndex, obsPoints, modelMatrix2] = await Promise.all([
357946
- this.loadObsIndex(),
357947
- this.loadPoints(),
357948
- this.loadModelMatrix()
358446
+ const [modelMatrix2, supportsTiling] = await Promise.all([
358447
+ this.loadModelMatrix(),
358448
+ this.supportsTiling()
357949
358449
  ]);
357950
358450
  const coordinationValues = {
357951
358451
  pointLayer: CL({
@@ -357961,7 +358461,36 @@ class SpatialDataObsPointsLoader extends AbstractTwoStepLoader {
357961
358461
  // additionalObsSets: null,
357962
358462
  })
357963
358463
  };
357964
- return new LoaderResult({ obsIndex, obsPoints, obsPointsModelMatrix: modelMatrix2 }, null, coordinationValues);
358464
+ let obsIndex = null;
358465
+ let obsPoints = null;
358466
+ const featureIndices = null;
358467
+ if (!supportsTiling) {
358468
+ [obsIndex, obsPoints] = await Promise.all([
358469
+ this.loadObsIndex(),
358470
+ this.loadPoints()
358471
+ ]);
358472
+ }
358473
+ return new LoaderResult({
358474
+ /* obsIndex: ["1"], // TEMP
358475
+ obsPoints: { // TEMP
358476
+ shape: [2, 1],
358477
+ data: [[0], [0]],
358478
+ }, */
358479
+ // These will be null if tiling is supported.
358480
+ obsIndex,
358481
+ obsPoints,
358482
+ featureIndices,
358483
+ obsPointsModelMatrix: modelMatrix2,
358484
+ // Return 'tiled' if the morton_code_2d column
358485
+ // and bounding_box metadata are present,
358486
+ // and the row group size is small enough.
358487
+ // Otherwise, return 'full'.
358488
+ obsPointsTilingType: supportsTiling ? "tiled" : "full",
358489
+ // TEMPORARY: probably makes more sense to pass the loader instance all the way down.
358490
+ // Caller can then decide whether to use loader.load vs. loader.loadPointsInRect.
358491
+ // May need another function such as loader.supportsPointInRect() true/false.
358492
+ loadPointsInRect: this.loadPointsInRect.bind(this)
358493
+ }, null, coordinationValues);
357965
358494
  }
357966
358495
  }
357967
358496
  class SpatialDataObsSetsLoader extends ObsSetsAnndataLoader {
@@ -367356,13 +367885,470 @@ function tableFromIPC(input) {
367356
367885
  }
367357
367886
  return new Table(reader.readAll());
367358
367887
  }
367888
+ const MORTON_CODE_NUM_BITS = 32;
367889
+ const MORTON_CODE_VALUE_MAX = 2 ** (MORTON_CODE_NUM_BITS / 2) - 1;
367890
+ function origCoordToNormCoord(origCoord, origXMin, origXMax, origYMin, origYMax) {
367891
+ const [origX, origY] = origCoord;
367892
+ const origXRange = origXMax - origXMin;
367893
+ const origYRange = origYMax - origYMin;
367894
+ return [
367895
+ // Clamp to zero at low end, since using unsigned ints.
367896
+ Math.max(Math.floor((origX - origXMin) / origXRange * MORTON_CODE_VALUE_MAX), 0),
367897
+ Math.max(Math.floor((origY - origYMin) / origYRange * MORTON_CODE_VALUE_MAX), 0)
367898
+ ];
367899
+ }
367900
+ function intersects$1(ax0, ay0, ax1, ay1, bx0, by0, bx1, by1) {
367901
+ return !(ax1 < bx0 || bx1 < ax0 || ay1 < by0 || by1 < ay0);
367902
+ }
367903
+ function contained(ix0, iy0, ix1, iy1, ox0, oy0, ox1, oy1) {
367904
+ return ox0 <= ix0 && ix0 <= ix1 && ix1 <= ox1 && (oy0 <= iy0 && iy0 <= iy1 && iy1 <= oy1);
367905
+ }
367906
+ function pointInside(x2, y2, rx0, ry0, rx1, ry1) {
367907
+ return rx0 <= x2 && x2 <= rx1 && (ry0 <= y2 && y2 <= ry1);
367908
+ }
367909
+ function cellRange(prefix2, level, bits) {
367910
+ const shift2 = 2 * (bits - level);
367911
+ const power = 2 ** shift2;
367912
+ const lo = prefix2 * power;
367913
+ const hi = (prefix2 + 1) * power - 1;
367914
+ return [lo, hi];
367915
+ }
367916
+ function mergeAdjacent(intervals2) {
367917
+ if (intervals2.length === 0) {
367918
+ return [];
367919
+ }
367920
+ intervals2.sort((a2, b2) => a2[0] - b2[0]);
367921
+ const merged = [intervals2[0]];
367922
+ for (let i2 = 1; i2 < intervals2.length; i2++) {
367923
+ const [lo, hi] = intervals2[i2];
367924
+ const [mlo, mhi] = merged[merged.length - 1];
367925
+ if (lo <= mhi + 1) {
367926
+ merged[merged.length - 1] = [mlo, Math.max(mhi, hi)];
367927
+ } else {
367928
+ merged.push([lo, hi]);
367929
+ }
367930
+ }
367931
+ return merged;
367932
+ }
367933
+ function zcoverRectangle(rx0, ry0, rx1, ry1, bits, stopLevel = null, merge2 = true) {
367934
+ const maxCoord = (1 << bits) - 1;
367935
+ if (!(rx0 >= 0 && rx0 <= rx1 && rx1 <= maxCoord && ry0 >= 0 && ry0 <= ry1 && ry1 <= maxCoord)) {
367936
+ throw new Error("Rectangle out of bounds for given bits.");
367937
+ }
367938
+ const intervals2 = [];
367939
+ const stack2 = [[0, 0, 0, 0, maxCoord, maxCoord]];
367940
+ while (stack2.length > 0) {
367941
+ const [prefix2, level, xmin, ymin, xmax, ymax] = stack2.pop();
367942
+ if (!intersects$1(xmin, ymin, xmax, ymax, rx0, ry0, rx1, ry1)) {
367943
+ continue;
367944
+ }
367945
+ if (stopLevel !== null && level === stopLevel) {
367946
+ intervals2.push(cellRange(prefix2, level, bits));
367947
+ continue;
367948
+ }
367949
+ if (contained(xmin, ymin, xmax, ymax, rx0, ry0, rx1, ry1)) {
367950
+ intervals2.push(cellRange(prefix2, level, bits));
367951
+ continue;
367952
+ }
367953
+ if (level === bits) {
367954
+ if (pointInside(xmin, ymin, rx0, ry0, rx1, ry1)) {
367955
+ intervals2.push(cellRange(prefix2, level, bits));
367956
+ }
367957
+ continue;
367958
+ }
367959
+ const midx = Math.floor((xmin + xmax) / 2);
367960
+ const midy = Math.floor((ymin + ymax) / 2);
367961
+ stack2.push([prefix2 << 2 | 0, level + 1, xmin, ymin, midx, midy]);
367962
+ stack2.push([prefix2 << 2 | 1, level + 1, midx + 1, ymin, xmax, midy]);
367963
+ stack2.push([prefix2 << 2 | 2, level + 1, xmin, midy + 1, midx, ymax]);
367964
+ stack2.push([prefix2 << 2 | 3, level + 1, midx + 1, midy + 1, xmax, ymax]);
367965
+ }
367966
+ return merge2 ? mergeAdjacent(intervals2) : intervals2;
367967
+ }
367968
+ function sdataMortonQueryRectAux(boundingBox, origRect) {
367969
+ const xMin = boundingBox.x_min;
367970
+ const xMax = boundingBox.x_max;
367971
+ const yMin = boundingBox.y_min;
367972
+ const yMax = boundingBox.y_max;
367973
+ const normRect = [
367974
+ origCoordToNormCoord(origRect[0], xMin, xMax, yMin, yMax),
367975
+ origCoordToNormCoord(origRect[1], xMin, xMax, yMin, yMax)
367976
+ ];
367977
+ const mortonIntervals = zcoverRectangle(normRect[0][0], normRect[0][1], normRect[1][0], normRect[1][1], 16, null, true);
367978
+ return mortonIntervals;
367979
+ }
367359
367980
  async function getParquetModule() {
367360
367981
  const module2 = await import(
367361
367982
  /* webpackIgnore: true */
367362
- "https://unpkg.com/parquet-wasm@0.6.1/esm/parquet_wasm.js"
367983
+ "https://cdn.vitessce.io/parquet-wasm@2c23652/esm/parquet_wasm.js"
367363
367984
  );
367364
367985
  await module2.default();
367365
- return { readParquet: module2.readParquet, readSchema: module2.readSchema };
367986
+ return {
367987
+ readParquet: module2.readParquet,
367988
+ readSchema: module2.readSchema,
367989
+ readMetadata: module2.readMetadata,
367990
+ // Added in fork
367991
+ readParquetRowGroup: module2.readParquetRowGroup
367992
+ // Added in fork
367993
+ };
367994
+ }
367995
+ async function _getParquetModule({ queryClient }) {
367996
+ return queryClient.fetchQuery({
367997
+ queryKey: ["parquetModule"],
367998
+ staleTime: Infinity,
367999
+ queryFn: getParquetModule,
368000
+ meta: { queryClient }
368001
+ });
368002
+ }
368003
+ async function _loadParquetBytes({ queryClient, store }, parquetPath, rangeQuery = void 0, partIndex = void 0) {
368004
+ return queryClient.fetchQuery({
368005
+ queryKey: ["SpatialDataTableSource", "_loadParquetBytes", parquetPath, rangeQuery, partIndex],
368006
+ staleTime: Infinity,
368007
+ queryFn: async (ctx) => {
368008
+ const store2 = ctx.meta?.store;
368009
+ const rangeQuery2 = ctx.queryKey[3];
368010
+ const { offset: offset2, length: length2, suffixLength } = rangeQuery2 || {};
368011
+ let getter2 = (path2) => store2.get(path2);
368012
+ if (rangeQuery2 !== void 0 && store2.getRange) {
368013
+ if (suffixLength !== void 0) {
368014
+ getter2 = (path2) => store2.getRange(path2, {
368015
+ suffixLength
368016
+ });
368017
+ } else {
368018
+ getter2 = (path2) => store2.getRange(path2, {
368019
+ offset: offset2,
368020
+ length: length2
368021
+ });
368022
+ }
368023
+ }
368024
+ let parquetBytes;
368025
+ if (partIndex === void 0) {
368026
+ parquetBytes = await getter2(`/${parquetPath}`);
368027
+ }
368028
+ if (!parquetBytes) {
368029
+ const part0Path = `${parquetPath}/part.${partIndex ?? 0}.parquet`;
368030
+ parquetBytes = await getter2(`/${part0Path}`);
368031
+ }
368032
+ return parquetBytes;
368033
+ },
368034
+ meta: { store }
368035
+ });
368036
+ }
368037
+ async function _loadParquetSchemaBytes({ queryClient, store }, parquetPath, partIndex = void 0) {
368038
+ return queryClient.fetchQuery({
368039
+ queryKey: ["SpatialDataTableSource", "_loadParquetSchemaBytes", parquetPath, partIndex],
368040
+ staleTime: Infinity,
368041
+ queryFn: async (ctx) => {
368042
+ const store2 = ctx.meta?.store;
368043
+ if (store2.getRange) {
368044
+ const TAIL_LENGTH = 8;
368045
+ let partZeroPath = parquetPath;
368046
+ let tailBytes = await store2.getRange(`/${partZeroPath}`, {
368047
+ suffixLength: TAIL_LENGTH
368048
+ });
368049
+ if (!tailBytes) {
368050
+ partZeroPath = `${parquetPath}/part.${partIndex ?? 0}.parquet`;
368051
+ tailBytes = await store2.getRange(`/${partZeroPath}`, {
368052
+ suffixLength: TAIL_LENGTH
368053
+ });
368054
+ }
368055
+ if (!tailBytes || tailBytes.length < TAIL_LENGTH) {
368056
+ throw new Error(`Failed to load parquet footerLength for ${partZeroPath}`);
368057
+ }
368058
+ const footerLength = new DataView(tailBytes.buffer).getInt32(0, true);
368059
+ const magic = new TextDecoder().decode(tailBytes.slice(4, 8));
368060
+ if (magic !== "PAR1") {
368061
+ throw new Error("Invalid Parquet file: missing PAR1 magic number");
368062
+ }
368063
+ const footerBytes = await store2.getRange(`/${partZeroPath}`, {
368064
+ suffixLength: footerLength + TAIL_LENGTH
368065
+ });
368066
+ if (!footerBytes || footerBytes.length !== footerLength + TAIL_LENGTH) {
368067
+ throw new Error(`Failed to load parquet footer bytes for ${parquetPath}`);
368068
+ }
368069
+ return footerBytes;
368070
+ }
368071
+ return null;
368072
+ },
368073
+ meta: { queryClient, store }
368074
+ });
368075
+ }
368076
+ async function _loadParquetMetadataByPart({ queryClient, store }, parquetPath) {
368077
+ return queryClient.fetchQuery({
368078
+ queryKey: ["SpatialDataTableSource", "_loadParquetMetadataByPart", parquetPath],
368079
+ staleTime: Infinity,
368080
+ queryFn: async (ctx) => {
368081
+ const queryClient2 = (
368082
+ /** @type {QueryClient} */
368083
+ ctx.meta?.queryClient
368084
+ );
368085
+ const { readSchema, readMetadata } = await _getParquetModule({ queryClient: queryClient2 });
368086
+ let partIndex = 0;
368087
+ let numParts;
368088
+ const allMetadata = [];
368089
+ do {
368090
+ try {
368091
+ const schemaBytes = await _loadParquetSchemaBytes({ queryClient: queryClient2, store }, parquetPath, partIndex);
368092
+ if (schemaBytes) {
368093
+ const wasmSchema = readSchema(schemaBytes);
368094
+ const arrowTableForSchema = tableFromIPC(wasmSchema.intoIPCStream());
368095
+ const partMetadata = readMetadata(schemaBytes);
368096
+ const partInfo = {
368097
+ schema: arrowTableForSchema,
368098
+ schemaBytes,
368099
+ metadata: partMetadata
368100
+ };
368101
+ allMetadata.push(partInfo);
368102
+ partIndex += 1;
368103
+ }
368104
+ } catch (error2) {
368105
+ if (error2.message.includes("Failed to load parquet footerLength")) {
368106
+ numParts = partIndex;
368107
+ }
368108
+ }
368109
+ } while (numParts === void 0);
368110
+ const metadata2 = {
368111
+ numRows: 0,
368112
+ numRowGroups: 0,
368113
+ numRowsPerGroup: 0,
368114
+ schema: null
368115
+ };
368116
+ if (allMetadata.length > 0) {
368117
+ const firstPart = allMetadata[0];
368118
+ metadata2.numRows = allMetadata.reduce((sum2, part) => sum2 + part.metadata.fileMetadata().numRows(), 0);
368119
+ metadata2.numRowGroups = allMetadata.reduce((sum2, part) => sum2 + part.metadata.numRowGroups(), 0);
368120
+ metadata2.numRowsPerGroup = firstPart.metadata.rowGroup(0).numRows();
368121
+ metadata2.schema = firstPart.schema.schema;
368122
+ }
368123
+ const result = {
368124
+ ...metadata2,
368125
+ // TODO: extract metadata per part and rowGroup into plain objects that match the hyparquet parquetMetadata() return value?
368126
+ // This will also make it easier to test.
368127
+ parts: allMetadata
368128
+ };
368129
+ return result;
368130
+ },
368131
+ meta: { queryClient }
368132
+ });
368133
+ }
368134
+ async function _loadParquetRowGroupByGroupIndex({ queryClient, store }, parquetPath, rowGroupIndex) {
368135
+ return queryClient.fetchQuery({
368136
+ queryKey: ["SpatialDataTableSource", "_loadParquetRowGroupByGroupIndex", parquetPath, rowGroupIndex],
368137
+ staleTime: Infinity,
368138
+ queryFn: async (ctx) => {
368139
+ const queryClient2 = (
368140
+ /** @type {QueryClient} */
368141
+ ctx.meta?.queryClient
368142
+ );
368143
+ const store2 = ctx.meta?.store;
368144
+ const { readParquetRowGroup } = await _getParquetModule({ queryClient: queryClient2 });
368145
+ const allMetadata = await _loadParquetMetadataByPart({ queryClient: queryClient2, store: store2 }, parquetPath);
368146
+ if (rowGroupIndex < 0 || rowGroupIndex >= allMetadata.numRowGroups) {
368147
+ throw new Error(`Row group index ${rowGroupIndex} is out of bounds for parquet table with ${allMetadata.numRowGroups} row groups.`);
368148
+ }
368149
+ let partIndex;
368150
+ let cumulativeRowGroups = 0;
368151
+ for (let i2 = 0; i2 < allMetadata.parts.length; i2++) {
368152
+ const part = allMetadata.parts[i2];
368153
+ const numRowGroupsInPart = part.metadata.numRowGroups();
368154
+ if (rowGroupIndex < cumulativeRowGroups + numRowGroupsInPart) {
368155
+ partIndex = i2;
368156
+ break;
368157
+ }
368158
+ cumulativeRowGroups += numRowGroupsInPart;
368159
+ }
368160
+ if (partIndex === void 0) {
368161
+ throw new Error(`Failed to find part containing row group index ${rowGroupIndex}.`);
368162
+ }
368163
+ const partMetadata = allMetadata.parts[partIndex].metadata;
368164
+ const { schemaBytes } = allMetadata.parts[partIndex];
368165
+ const rowGroupIndexRelativeToPart = rowGroupIndex - cumulativeRowGroups;
368166
+ const rowGroupMetadata = partMetadata.rowGroup(rowGroupIndexRelativeToPart);
368167
+ const rowGroupFileOffset = rowGroupMetadata.fileOffset();
368168
+ const rowGroupCompressedSize = rowGroupMetadata.compressedSize();
368169
+ const rowGroupBytes = await _loadParquetBytes({ queryClient: queryClient2, store: store2 }, parquetPath, { offset: rowGroupFileOffset, length: rowGroupCompressedSize }, partIndex);
368170
+ const rowGroupIPC = readParquetRowGroup(schemaBytes, rowGroupBytes, rowGroupIndexRelativeToPart).intoIPCStream();
368171
+ const rowGroupTable = tableFromIPC(rowGroupIPC);
368172
+ return rowGroupTable;
368173
+ },
368174
+ meta: { queryClient, store }
368175
+ });
368176
+ }
368177
+ async function _loadParquetRowGroupColumnExtent({ queryClient, store }, parquetPath, columnName, rowGroupIndex) {
368178
+ return queryClient.fetchQuery({
368179
+ queryKey: ["SpatialDataTableSource", "_loadParquetRowGroupColumnExtent", parquetPath, columnName, rowGroupIndex],
368180
+ staleTime: Infinity,
368181
+ queryFn: async (ctx) => {
368182
+ const queryClient2 = (
368183
+ /** @type {QueryClient} */
368184
+ ctx.meta?.queryClient
368185
+ );
368186
+ const store2 = ctx.meta?.store;
368187
+ const rowGroupTable = await _loadParquetRowGroupByGroupIndex({ queryClient: queryClient2, store: store2 }, parquetPath, rowGroupIndex);
368188
+ const column2 = rowGroupTable.getChild(columnName);
368189
+ if (!column2) {
368190
+ throw new Error(`Column ${columnName} not found in row group ${rowGroupIndex} of parquet table at ${parquetPath}.`);
368191
+ }
368192
+ if (column2.length === 0) {
368193
+ return { min: null, max: null };
368194
+ }
368195
+ return { min: column2.get(0), max: column2.get(column2.length - 1) };
368196
+ },
368197
+ meta: { queryClient, store }
368198
+ });
368199
+ }
368200
+ function getCachedInRangeSync(queryClient, parquetPath, columnName, lo, hi) {
368201
+ const queryCache = queryClient.getQueryCache();
368202
+ const prevQueries = queryCache.findAll({
368203
+ // Note: Must (manually) keep in sync with queryKey used in _loadParquetRowGroupColumnExtent.
368204
+ queryKey: ["SpatialDataTableSource", "_loadParquetRowGroupColumnExtent", parquetPath, columnName],
368205
+ exact: false
368206
+ });
368207
+ const cachedRowGroupInfo = prevQueries.map((q) => {
368208
+ if (!(q.state.status === "success" && !q.state.isInvalidated)) {
368209
+ return null;
368210
+ }
368211
+ return {
368212
+ queryKey: q.queryKey,
368213
+ index: q.queryKey[4],
368214
+ status: q.state.status,
368215
+ min: q.state.data?.min,
368216
+ max: q.state.data?.max
368217
+ };
368218
+ }).filter((v) => v !== null).toSorted((a2, b2) => a2.index - b2.index);
368219
+ return cachedRowGroupInfo.filter((c2) => c2.index >= lo && c2.index < hi);
368220
+ }
368221
+ async function getCachedInRange(queryClient, parquetPath, columnName, lo, hi) {
368222
+ const queryCache = queryClient.getQueryCache();
368223
+ const prevQueries = queryCache.findAll({
368224
+ // Note: Must (manually) keep in sync with queryKey used in _loadParquetRowGroupColumnExtent.
368225
+ queryKey: ["SpatialDataTableSource", "_loadParquetRowGroupColumnExtent", parquetPath, columnName],
368226
+ exact: false
368227
+ });
368228
+ const cachedRowGroupInfo = prevQueries.map((q) => ({
368229
+ queryKey: q.queryKey,
368230
+ index: q.queryKey[4],
368231
+ status: q.state.status,
368232
+ min: q.state.data?.min,
368233
+ max: q.state.data?.max
368234
+ })).filter((v) => v !== null).toSorted((a2, b2) => a2.index - b2.index);
368235
+ const cachedInRange = cachedRowGroupInfo.filter((c2) => c2.index >= lo && c2.index < hi);
368236
+ const pendingQueries = cachedInRange.filter((c2) => c2.status !== "success");
368237
+ if (pendingQueries.length === 0) {
368238
+ return cachedInRange;
368239
+ }
368240
+ const pendingPromises = pendingQueries.map((c2) => queryClient.ensureQueryData({
368241
+ queryKey: c2.queryKey
368242
+ }));
368243
+ await Promise.all(pendingPromises);
368244
+ return getCachedInRangeSync(queryClient, parquetPath, columnName, lo, hi);
368245
+ }
368246
+ async function _bisectRowGroupsRight({ queryClient, store }, parquetPath, columnName, targetValue) {
368247
+ return queryClient.fetchQuery({
368248
+ queryKey: ["SpatialDataTableSource", "_bisectRowGroupsRight", parquetPath, columnName, targetValue],
368249
+ staleTime: Infinity,
368250
+ queryFn: async (ctx) => {
368251
+ const queryClient2 = (
368252
+ /** @type {QueryClient} */
368253
+ ctx.meta?.queryClient
368254
+ );
368255
+ const store2 = ctx.meta?.store;
368256
+ const allMetadata = await _loadParquetMetadataByPart({ queryClient: queryClient2, store: store2 }, parquetPath);
368257
+ const { numRowGroups } = allMetadata;
368258
+ let lo = 0;
368259
+ let hi = numRowGroups;
368260
+ while (lo < hi) {
368261
+ const cachedInRange = await getCachedInRange(queryClient2, parquetPath, columnName, lo, hi);
368262
+ const betterLo = cachedInRange.slice().reverse().find((c2) => c2.index > lo && targetValue >= c2.max);
368263
+ if (betterLo) {
368264
+ lo = Math.min(hi, betterLo.index + 1);
368265
+ }
368266
+ const betterHi = cachedInRange.find((c2) => targetValue < c2.min);
368267
+ if (betterHi) {
368268
+ hi = Math.max(lo, betterHi.index - 1);
368269
+ }
368270
+ if (lo >= hi) {
368271
+ break;
368272
+ }
368273
+ const mid = Math.floor((lo + hi) / 2);
368274
+ const { max: midVal } = await _loadParquetRowGroupColumnExtent({ queryClient: queryClient2, store: store2 }, parquetPath, columnName, mid);
368275
+ if (midVal === null || targetValue <= midVal) {
368276
+ hi = mid;
368277
+ } else {
368278
+ lo = mid + 1;
368279
+ }
368280
+ }
368281
+ return lo;
368282
+ },
368283
+ meta: { queryClient, store }
368284
+ });
368285
+ }
368286
+ async function _rectToRowGroupIndices({ queryClient, store }, parquetPath, tileBbox, allPointsBbox) {
368287
+ return queryClient.fetchQuery({
368288
+ queryKey: ["SpatialDataTableSource", "_rectToRowGroupIndices", parquetPath, tileBbox, allPointsBbox],
368289
+ staleTime: Infinity,
368290
+ queryFn: async (ctx) => {
368291
+ const queryClient2 = (
368292
+ /** @type {QueryClient} */
368293
+ ctx.meta?.queryClient
368294
+ );
368295
+ const store2 = ctx.meta?.store;
368296
+ const mortonIntervals = sdataMortonQueryRectAux(allPointsBbox, [
368297
+ [tileBbox.left, tileBbox.top],
368298
+ // TODO: is this backwards (bottom/top)?
368299
+ [tileBbox.right, tileBbox.bottom]
368300
+ ]);
368301
+ let coveredRowGroupIndices = [];
368302
+ const intervalsSpanMultipleRowGroups = async (startIndex, endIndex) => {
368303
+ if (startIndex > endIndex) {
368304
+ return [false, null];
368305
+ }
368306
+ const [startMin, startMax] = mortonIntervals[startIndex];
368307
+ const [endMin, endMax] = mortonIntervals[endIndex];
368308
+ const [rowGroupIndexMin, rowGroupIndexMax] = await Promise.all([
368309
+ _bisectRowGroupsRight({ queryClient: queryClient2, store: store2 }, parquetPath, "morton_code_2d", startMin),
368310
+ _bisectRowGroupsRight({ queryClient: queryClient2, store: store2 }, parquetPath, "morton_code_2d", endMax)
368311
+ ]);
368312
+ if (rowGroupIndexMin === rowGroupIndexMax) {
368313
+ return [false, [rowGroupIndexMin]];
368314
+ }
368315
+ if (rowGroupIndexMin + 1 === rowGroupIndexMax || rowGroupIndexMin - 1 === rowGroupIndexMax) {
368316
+ return [false, [rowGroupIndexMin, rowGroupIndexMax]];
368317
+ }
368318
+ return [true, null];
368319
+ };
368320
+ const intervalIndicesToCheck = [[0, mortonIntervals.length - 1]];
368321
+ while (intervalIndicesToCheck.length > 0) {
368322
+ const [startIndex, endIndex] = intervalIndicesToCheck.pop();
368323
+ const [spansMultipleRowGroups, rowGroupIndices] = await intervalsSpanMultipleRowGroups(startIndex, endIndex);
368324
+ if (!spansMultipleRowGroups) {
368325
+ if (rowGroupIndices !== null) {
368326
+ coveredRowGroupIndices = coveredRowGroupIndices.concat(rowGroupIndices);
368327
+ }
368328
+ } else {
368329
+ if (startIndex === endIndex) {
368330
+ const [intervalMin, intervalMax] = mortonIntervals[startIndex];
368331
+ const [rowGroupIndexMin, rowGroupIndexMax] = await Promise.all([
368332
+ _bisectRowGroupsRight({ queryClient: queryClient2, store: store2 }, parquetPath, "morton_code_2d", intervalMin),
368333
+ _bisectRowGroupsRight({ queryClient: queryClient2, store: store2 }, parquetPath, "morton_code_2d", intervalMax)
368334
+ ]);
368335
+ if (rowGroupIndexMin <= rowGroupIndexMax) {
368336
+ coveredRowGroupIndices = coveredRowGroupIndices.concat(range$e(rowGroupIndexMin, rowGroupIndexMax + 1));
368337
+ } else {
368338
+ coveredRowGroupIndices = coveredRowGroupIndices.concat(range$e(rowGroupIndexMax, rowGroupIndexMin + 1));
368339
+ }
368340
+ } else {
368341
+ const midIndex = Math.floor((startIndex + endIndex) / 2);
368342
+ intervalIndicesToCheck.push([startIndex, midIndex]);
368343
+ intervalIndicesToCheck.push([midIndex + 1, endIndex]);
368344
+ }
368345
+ }
368346
+ }
368347
+ const uniqueCoveredRowGroupIndices = Array.from(new Set(coveredRowGroupIndices));
368348
+ return uniqueCoveredRowGroupIndices;
368349
+ },
368350
+ meta: { queryClient, store }
368351
+ });
367366
368352
  }
367367
368353
  function tableToIndexColumnName(arrowTable) {
367368
368354
  const pandasMetadata = arrowTable.schema.metadata.get("pandas");
@@ -367416,14 +368402,17 @@ function getVarPath(arrPath) {
367416
368402
  class SpatialDataTableSource extends AnnDataSource {
367417
368403
  /**
367418
368404
  *
367419
- * @param {DataSourceParams} params
368405
+ * @param {DataSourceParams & { queryClient: QueryClient }} params
367420
368406
  */
367421
368407
  constructor(params2) {
367422
368408
  super(params2);
368409
+ const { queryClient } = params2;
368410
+ this.queryClient = queryClient;
367423
368411
  this.parquetModulePromise = getParquetModule();
367424
368412
  this.rootAttrs = null;
367425
368413
  this.elementAttrs = {};
367426
368414
  this.parquetTableBytes = {};
368415
+ this.parquetTableIsDirectory = {};
367427
368416
  this.obsIndices = {};
367428
368417
  this.varIndices = {};
367429
368418
  this.varAliases = {};
@@ -367477,17 +368466,19 @@ class SpatialDataTableSource extends AnnDataSource {
367477
368466
  * relative to the store root.
367478
368467
  * @returns {Promise<Uint8Array|undefined>} The parquet file bytes.
367479
368468
  */
367480
- async loadParquetBytes(parquetPath) {
367481
- if (this.parquetTableBytes[parquetPath]) {
367482
- return this.parquetTableBytes[parquetPath];
368469
+ async loadParquetBytes(parquetPath, offset2 = void 0, length2 = void 0, partIndex = void 0) {
368470
+ const { store } = this.storeRoot;
368471
+ let getter2 = (path2) => store.get(path2);
368472
+ if (offset2 !== void 0 && length2 !== void 0 && store.getRange) {
368473
+ getter2 = (path2) => store.getRange(path2, {
368474
+ offset: offset2,
368475
+ length: length2
368476
+ });
367483
368477
  }
367484
- let parquetBytes = await this.storeRoot.store.get(`/${parquetPath}`);
368478
+ let parquetBytes = await getter2(`/${parquetPath}`);
367485
368479
  if (!parquetBytes) {
367486
- const part0Path = `${parquetPath}/part.0.parquet`;
367487
- parquetBytes = await this.storeRoot.store.get(`/${part0Path}`);
367488
- }
367489
- if (parquetBytes) {
367490
- this.parquetTableBytes[parquetPath] = parquetBytes;
368480
+ const part0Path = `${parquetPath}/part.${partIndex ?? 0}.parquet`;
368481
+ parquetBytes = await getter2(`/${part0Path}`);
367491
368482
  }
367492
368483
  return parquetBytes;
367493
368484
  }
@@ -367506,7 +368497,7 @@ class SpatialDataTableSource extends AnnDataSource {
367506
368497
  * @returns {Promise<Uint8Array|null>} The parquet file bytes,
367507
368498
  * or null if the store does not support getRange.
367508
368499
  */
367509
- async loadParquetSchemaBytes(parquetPath) {
368500
+ async loadParquetSchemaBytes(parquetPath, partIndex = void 0) {
367510
368501
  const { store } = this.storeRoot;
367511
368502
  if (store.getRange) {
367512
368503
  const TAIL_LENGTH = 8;
@@ -367514,12 +368505,10 @@ class SpatialDataTableSource extends AnnDataSource {
367514
368505
  let tailBytes = await store.getRange(`/${partZeroPath}`, {
367515
368506
  suffixLength: TAIL_LENGTH
367516
368507
  });
367517
- if (!tailBytes) {
367518
- partZeroPath = `${parquetPath}/part.0.parquet`;
367519
- tailBytes = await store.getRange(`/${partZeroPath}`, {
367520
- suffixLength: TAIL_LENGTH
367521
- });
367522
- }
368508
+ partZeroPath = `${parquetPath}/part.${partIndex ?? 0}.parquet`;
368509
+ tailBytes = await store.getRange(`/${partZeroPath}`, {
368510
+ suffixLength: TAIL_LENGTH
368511
+ });
367523
368512
  if (!tailBytes || tailBytes.length < TAIL_LENGTH) {
367524
368513
  throw new Error(`Failed to load parquet footerLength for ${parquetPath}`);
367525
368514
  }
@@ -367581,7 +368570,7 @@ class SpatialDataTableSource extends AnnDataSource {
367581
368570
  const schemaBytes = await this.loadParquetSchemaBytes(parquetPath);
367582
368571
  if (schemaBytes) {
367583
368572
  const wasmSchema = readSchema(schemaBytes);
367584
- const arrowTableForSchema = await tableFromIPC(wasmSchema.intoIPCStream());
368573
+ const arrowTableForSchema = tableFromIPC(wasmSchema.intoIPCStream());
367585
368574
  indexColumnName = tableToIndexColumnName(arrowTableForSchema);
367586
368575
  }
367587
368576
  } catch (e3) {
@@ -367597,14 +368586,14 @@ class SpatialDataTableSource extends AnnDataSource {
367597
368586
  }
367598
368587
  if (columns2 && !indexColumnName) {
367599
368588
  const wasmSchema = readSchema(parquetBytes);
367600
- const arrowTableForSchema = await tableFromIPC(wasmSchema.intoIPCStream());
368589
+ const arrowTableForSchema = tableFromIPC(wasmSchema.intoIPCStream());
367601
368590
  indexColumnName = tableToIndexColumnName(arrowTableForSchema);
367602
368591
  }
367603
368592
  if (options.columns && indexColumnName) {
367604
368593
  options.columns = [...options.columns, indexColumnName];
367605
368594
  }
367606
368595
  const wasmTable = readParquet(parquetBytes, options);
367607
- const arrowTable = await tableFromIPC(wasmTable.intoIPCStream());
368596
+ const arrowTable = tableFromIPC(wasmTable.intoIPCStream());
367608
368597
  return arrowTable;
367609
368598
  }
367610
368599
  // TABLE-SPECIFIC METHODS
@@ -367668,6 +368657,93 @@ class SpatialDataTableSource extends AnnDataSource {
367668
368657
  this.varAliases[varPath] = this.varAliases[varPath].map((val, ind) => val ? val.concat(` (${index2[ind]})`) : index2[ind]);
367669
368658
  return this.varAliases[varPath];
367670
368659
  }
368660
+ async _supportsTiledPoints(parquetPath) {
368661
+ const { queryClient } = this;
368662
+ const { store } = this.storeRoot;
368663
+ const allMetadata = await _loadParquetMetadataByPart({ queryClient, store }, parquetPath);
368664
+ const { numRowsPerGroup } = allMetadata;
368665
+ const numRowsTotal = allMetadata.numRows;
368666
+ if (numRowsPerGroup >= 1e5) {
368667
+ if (numRowsTotal > 5e6) {
368668
+ 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.`);
368669
+ }
368670
+ return false;
368671
+ }
368672
+ const requiredColumns = ["x", "y", "feature_index", "morton_code_2d"];
368673
+ const hasColumns = allMetadata?.schema?.fields?.map((f2) => f2.name);
368674
+ if (!hasColumns) {
368675
+ return false;
368676
+ }
368677
+ const hasRequiredColumns = requiredColumns.every((col) => hasColumns.includes(col));
368678
+ if (!hasRequiredColumns && numRowsTotal > 5e6) {
368679
+ 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.`);
368680
+ }
368681
+ return hasRequiredColumns;
368682
+ }
368683
+ /**
368684
+ * Load point data using a tiled approach.
368685
+ * @param {string} parquetPath A path to a parquet file (or directory).
368686
+ * @param {{ left: number, top: number, right: number, bottom: number }} tileBbox
368687
+ * @param {{ x_min: number, y_min: number, x_max: number, y_max: number }} allPointsBbox
368688
+ * @param {string[]|undefined} columns An optional list of column names to load.
368689
+ * @returns
368690
+ */
368691
+ async loadParquetTableInRect(parquetPath, tileBbox, allPointsBbox, signal) {
368692
+ const { queryClient } = this;
368693
+ const { store } = this.storeRoot;
368694
+ const TILE_SIZE2 = 256;
368695
+ let tileBboxes = [];
368696
+ if (tileBbox.right - tileBbox.left > TILE_SIZE2 || tileBbox.bottom - tileBbox.top > TILE_SIZE2) {
368697
+ const xSteps = Math.ceil((tileBbox.right - tileBbox.left) / TILE_SIZE2);
368698
+ const ySteps = Math.ceil((tileBbox.bottom - tileBbox.top) / TILE_SIZE2);
368699
+ const xStepSize = (tileBbox.right - tileBbox.left) / xSteps;
368700
+ const yStepSize = (tileBbox.bottom - tileBbox.top) / ySteps;
368701
+ for (let i2 = 0; i2 < xSteps; i2++) {
368702
+ for (let j = 0; j < ySteps; j++) {
368703
+ const subTileBbox = {
368704
+ left: tileBbox.left + i2 * xStepSize,
368705
+ right: Math.min(tileBbox.left + (i2 + 1) * xStepSize, tileBbox.right),
368706
+ top: tileBbox.top + j * yStepSize,
368707
+ bottom: Math.min(tileBbox.top + (j + 1) * yStepSize, tileBbox.bottom)
368708
+ };
368709
+ tileBboxes.push(subTileBbox);
368710
+ }
368711
+ }
368712
+ } else {
368713
+ tileBboxes = [tileBbox];
368714
+ }
368715
+ const rowGroupIndicesPerTile = await Promise.all(tileBboxes.map(async (subTileBbox) => _rectToRowGroupIndices({ queryClient, store }, parquetPath, subTileBbox, allPointsBbox)));
368716
+ const uniqueCoveredRowGroupIndices = Array.from(new Set(rowGroupIndicesPerTile.flat())).toSorted((a2, b2) => a2 - b2);
368717
+ const allMetadata = await _loadParquetMetadataByPart({ queryClient, store }, parquetPath);
368718
+ const { numRowsPerGroup } = allMetadata;
368719
+ const numRowGroups = uniqueCoveredRowGroupIndices.length;
368720
+ const totalNumRows = numRowsPerGroup * numRowGroups;
368721
+ const xArr = new Float32Array(totalNumRows);
368722
+ const yArr = new Float32Array(totalNumRows);
368723
+ const featureIndexArr = new Uint32Array(totalNumRows);
368724
+ const rowGroupTables = await Promise.all(uniqueCoveredRowGroupIndices.map(async (rowGroupIndex) => _loadParquetRowGroupByGroupIndex({ queryClient, store }, parquetPath, rowGroupIndex)));
368725
+ let rowOffset = 0;
368726
+ rowGroupTables.forEach((table2) => {
368727
+ const xColumn = table2.getChild("x");
368728
+ const yColumn = table2.getChild("y");
368729
+ const featureIndexColumn = table2.getChild("feature_index");
368730
+ if (!xColumn || !yColumn || !featureIndexColumn) {
368731
+ throw new Error(`Missing required column in parquet table at ${parquetPath}. Required columns: x, y, feature_index`);
368732
+ }
368733
+ xArr.set(xColumn.toArray(), rowOffset);
368734
+ yArr.set(yColumn.toArray(), rowOffset);
368735
+ featureIndexArr.set(featureIndexColumn.toArray(), rowOffset);
368736
+ rowOffset += numRowsPerGroup;
368737
+ });
368738
+ return {
368739
+ data: {
368740
+ x: xArr,
368741
+ y: yArr,
368742
+ featureIndices: featureIndexArr
368743
+ },
368744
+ shape: [3, totalNumRows]
368745
+ };
368746
+ }
367671
368747
  }
367672
368748
  class BaseEvent {
367673
368749
  /**
@@ -373823,6 +374899,33 @@ class SpatialDataPointsSource extends SpatialDataTableSource {
373823
374899
  data: axisColumnArrs
373824
374900
  };
373825
374901
  }
374902
+ /**
374903
+ *
374904
+ * @param {string} elementPath
374905
+ * @param {{ left: number, top: number, right: number, bottom: number }} tileBbox
374906
+ * @returns {Promise<{
374907
+ * data: [ZarrTypedArray<any>, ZarrTypedArray<any>],
374908
+ * shape: [number, number],
374909
+ * }>} A promise for a zarr array containing the data.
374910
+ */
374911
+ async loadPointsInRect(elementPath, tileBbox, signal) {
374912
+ const parquetPath = getParquetPath(elementPath);
374913
+ const zattrs = await this.loadSpatialDataElementAttrs(elementPath);
374914
+ const {
374915
+ // axes,
374916
+ // spatialdata_attrs: spatialDataAttrs,
374917
+ // The bounding box (extent) of all points.
374918
+ // Required for un-normalization from uints back to floats.
374919
+ // TODO: decide whether these will be stored here or somewhere else.
374920
+ // Reference: https://github.com/vitessce/vitessce-python/pull/476#issuecomment-3362656956
374921
+ bounding_box: allPointsBbox
374922
+ } = zattrs;
374923
+ return this.loadParquetTableInRect(parquetPath, tileBbox, allPointsBbox, signal);
374924
+ }
374925
+ async supportsLoadPointsInRect(elementPath) {
374926
+ const parquetPath = getParquetPath(elementPath);
374927
+ return this._supportsTiledPoints(parquetPath);
374928
+ }
373826
374929
  }
373827
374930
  class OmeTiffSource {
373828
374931
  constructor({ url, requestInit: requestInit2 }) {
@@ -379967,10 +381070,27 @@ const baseCoordinationTypes = [
379967
381070
  color: rgbArray
379968
381071
  })).nullable()
379969
381072
  ),
381073
+ new PluginCoordinationType(
381074
+ CoordinationType$1.FEATURE_COLOR,
381075
+ null,
381076
+ z.array(z.object({
381077
+ name: z.string(),
381078
+ color: rgbArray
381079
+ })).nullable()
381080
+ ),
379970
381081
  new PluginCoordinationType(
379971
381082
  CoordinationType$1.OBS_COLOR_ENCODING,
379972
381083
  "cellSetSelection",
379973
- z.enum(["geneSelection", "cellSetSelection", "spatialChannelColor", "spatialLayerColor", "obsLabels"])
381084
+ z.enum([
381085
+ "geneSelection",
381086
+ "cellSetSelection",
381087
+ "spatialChannelColor",
381088
+ "spatialLayerColor",
381089
+ "obsLabels",
381090
+ // For point coloring.
381091
+ "random",
381092
+ "randomByFeature"
381093
+ ])
379974
381094
  ),
379975
381095
  new PluginCoordinationType(CoordinationType$1.FEATURE_FILTER, null, z.array(z.string()).nullable()),
379976
381096
  new PluginCoordinationType(CoordinationType$1.FEATURE_HIGHLIGHT, null, z.string().nullable()),
@@ -380001,7 +381121,7 @@ const baseCoordinationTypes = [
380001
381121
  null,
380002
381122
  z.array(obsSetPath).nullable()
380003
381123
  ),
380004
- new PluginCoordinationType(CoordinationType$1.FEATURE_FILTER_MODE, null, z.enum(["featureFilter", "featureSetFilter"]).nullable()),
381124
+ new PluginCoordinationType(CoordinationType$1.FEATURE_FILTER_MODE, null, z.enum(["featureFilter", "featureSetFilter", "featureSelection"]).nullable()),
380005
381125
  new PluginCoordinationType(
380006
381126
  CoordinationType$1.FEATURE_VALUE_TRANSFORM,
380007
381127
  null,