@vitessce/all 3.8.13 → 3.9.1

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