@vitessce/scatterplot-gating 3.8.0 → 3.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import { i as inflate_1 } from "./pako.esm-SxljTded.js";
2
- import { B as BaseDecoder } from "./index-B4FTJe_L.js";
2
+ import { B as BaseDecoder } from "./index-BZoxwGBy.js";
3
3
  class DeflateDecoder extends BaseDecoder {
4
4
  decodeBlock(buffer) {
5
5
  return inflate_1(new Uint8Array(buffer)).buffer;
@@ -4,7 +4,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4
4
  var _a2, _b;
5
5
  import * as React from "react";
6
6
  import React__default, { useLayoutEffect, useEffect, cloneElement, createElement, forwardRef, useState, useRef, useMemo, useImperativeHandle, useContext, createContext, isValidElement, Children, PureComponent, useCallback } from "react";
7
- import { usePlotOptionsStyles, OptionsContainer, CellColorEncodingOption, OptionSelect, useVitessceContainer, useComponentHover, useComponentViewInfo, useLoaders, useCoordinationScopes, useSetComponentHover, useSetComponentViewInfo, useCoordination, useDeckCanvasSize, useObsSetsData, useFeatureSelection, useObsFeatureMatrixIndices, useReady, useUrls, useUint8FeatureSelection, useExpressionValueGetter, TitleInfo } from "@vitessce/vit-s";
7
+ import { usePlotOptionsStyles, OptionsContainer, CellColorEncodingOption, OptionSelect, useVitessceContainer, useComponentHover, useComponentViewInfo, useLoaders, useCoordinationScopes, useSetComponentHover, useSetComponentViewInfo, useCoordination, useInitialCoordination, useDeckCanvasSize, useObsSetsData, useFeatureSelection, useObsFeatureMatrixIndices, useReady, useUrls, useUint8FeatureSelection, useExpressionValueGetter, TitleInfo } from "@vitessce/vit-s";
8
8
  import * as ReactDOM from "react-dom";
9
9
  import ReactDOM__default from "react-dom";
10
10
  function _mergeNamespaces(n2, m2) {
@@ -9492,7 +9492,7 @@ const ViewType$1 = {
9492
9492
  SCATTERPLOT: "scatterplot",
9493
9493
  SPATIAL: "spatial",
9494
9494
  SPATIAL_BETA: "spatialBeta",
9495
- SPATIAL_ACCELERATED: "spatialAccelerated",
9495
+ // SPATIAL_ACCELERATED: 'spatialAccelerated',
9496
9496
  HEATMAP: "heatmap",
9497
9497
  LAYER_CONTROLLER: "layerController",
9498
9498
  LAYER_CONTROLLER_BETA: "layerControllerBeta",
@@ -9650,6 +9650,9 @@ const FileType$1 = {
9650
9650
  OBS_SEGMENTATIONS_OME_TIFF: "obsSegmentations.ome-tiff",
9651
9651
  // GLB
9652
9652
  OBS_SEGMENTATIONS_GLB: "obsSegmentations.glb",
9653
+ // Neuroglancer
9654
+ // Precomputed (mesh) format
9655
+ OBS_SEGMENTATIONS_NG_PRECOMPUTED: "obsSegmentations.ng-precomputed",
9653
9656
  // New file types to support old file types:
9654
9657
  // - cells.json
9655
9658
  OBS_EMBEDDING_CELLS_JSON: "obsEmbedding.cells.json",
@@ -9877,6 +9880,7 @@ const ViewHelpMapping = {
9877
9880
  [FileType$1.OBS_LABELS_MUDATA_ZARR]: DataType$2.OBS_LABELS,
9878
9881
  [FileType$1.FEATURE_LABELS_MUDATA_ZARR]: DataType$2.FEATURE_LABELS,
9879
9882
  [FileType$1.OBS_SEGMENTATIONS_GLB]: DataType$2.OBS_SEGMENTATIONS,
9883
+ [FileType$1.OBS_SEGMENTATIONS_NG_PRECOMPUTED]: DataType$2.OBS_SEGMENTATIONS,
9880
9884
  [FileType$1.IMAGE_SPATIALDATA_ZARR]: DataType$2.IMAGE,
9881
9885
  [FileType$1.LABELS_SPATIALDATA_ZARR]: DataType$2.OBS_SEGMENTATIONS,
9882
9886
  [FileType$1.SHAPES_SPATIALDATA_ZARR]: DataType$2.OBS_SEGMENTATIONS,
@@ -11140,6 +11144,18 @@ z.object({
11140
11144
  sceneScaleZ: z.number(),
11141
11145
  materialSide: z.enum(["front", "back"])
11142
11146
  }).partial().nullable();
11147
+ z.object({
11148
+ // TODO: Should this explicitly specify sharded vs. unsharded?
11149
+ // Or can/should that be inferred from the data?
11150
+ dimensionX: z.number(),
11151
+ dimensionY: z.number(),
11152
+ dimensionZ: z.number(),
11153
+ dimensionUnit: z.enum(["nm", "um", "µm", "mm", "cm", "m"]),
11154
+ // TODO: should the following be passed via coordination types instead?
11155
+ projectionScale: z.number(),
11156
+ position: z.array(z.number()).length(3),
11157
+ projectionOrientation: z.array(z.number()).length(4)
11158
+ }).partial().nullable();
11143
11159
  z.object({
11144
11160
  obsIndex: z.string(),
11145
11161
  obsEmbedding: z.array(z.string()).length(2)
@@ -123129,22 +123145,22 @@ function addDecoder(cases, importFn) {
123129
123145
  }
123130
123146
  cases.forEach((c2) => registry$1.set(c2, importFn));
123131
123147
  }
123132
- addDecoder([void 0, 1], () => import("./raw-BFCCI-Mm.js").then((m2) => m2.default));
123133
- addDecoder(5, () => import("./lzw-DiaKhPZe.js").then((m2) => m2.default));
123148
+ addDecoder([void 0, 1], () => import("./raw-C5EHR8Uv.js").then((m2) => m2.default));
123149
+ addDecoder(5, () => import("./lzw-DpnaIz4v.js").then((m2) => m2.default));
123134
123150
  addDecoder(6, () => {
123135
123151
  throw new Error("old style JPEG compression is not supported.");
123136
123152
  });
123137
- addDecoder(7, () => import("./jpeg-G-ewKEbP.js").then((m2) => m2.default));
123138
- addDecoder([8, 32946], () => import("./deflate-B6pWJWB8.js").then((m2) => m2.default));
123139
- addDecoder(32773, () => import("./packbits-CT7DC7Iq.js").then((m2) => m2.default));
123153
+ addDecoder(7, () => import("./jpeg-DnXWckxj.js").then((m2) => m2.default));
123154
+ addDecoder([8, 32946], () => import("./deflate-CKHV0khu.js").then((m2) => m2.default));
123155
+ addDecoder(32773, () => import("./packbits-DlOGmdZv.js").then((m2) => m2.default));
123140
123156
  addDecoder(
123141
123157
  34887,
123142
- () => import("./lerc-CoSyfsZl.js").then(async (m2) => {
123158
+ () => import("./lerc-DnC1d7LQ.js").then(async (m2) => {
123143
123159
  await m2.zstd.init();
123144
123160
  return m2;
123145
123161
  }).then((m2) => m2.default)
123146
123162
  );
123147
- addDecoder(50001, () => import("./webimage-BtSqKc33.js").then((m2) => m2.default));
123163
+ addDecoder(50001, () => import("./webimage-CXmDbYs_.js").then((m2) => m2.default));
123148
123164
  function decodeRowAcc(row, stride) {
123149
123165
  let length2 = row.length - stride;
123150
123166
  let offset2 = 0;
@@ -158139,13 +158155,13 @@ function IconButton(props) {
158139
158155
  function ToolMenu(props) {
158140
158156
  const pointerIconAltText = "Pointer tool";
158141
158157
  const lassoIconAltText = "Select lasso";
158142
- const { setActiveTool, activeTool, visibleTools = { pan: true, selectLasso: true }, recenterOnClick = () => {
158158
+ const { setActiveTool, activeTool, visibleTools = { pan: true, selectLasso: true, recenter: true }, recenterOnClick = () => {
158143
158159
  } } = props;
158144
158160
  const { classes } = useStyles$2();
158145
158161
  const onRecenterButtonCLick = () => {
158146
158162
  recenterOnClick();
158147
158163
  };
158148
- return jsxRuntimeExports.jsxs("div", { className: classes.tool, children: [visibleTools.pan && jsxRuntimeExports.jsx(IconTool, { alt: pointerIconAltText, onClick: () => setActiveTool(null), isActive: activeTool === null, children: jsxRuntimeExports.jsx(SvgNearMe, {}) }), visibleTools.selectLasso ? jsxRuntimeExports.jsx(IconTool, { alt: lassoIconAltText, onClick: () => setActiveTool(distEs6Exports$2.SELECTION_TYPE.POLYGON), isActive: activeTool === distEs6Exports$2.SELECTION_TYPE.POLYGON, children: jsxRuntimeExports.jsx(SvgSelectionLasso, {}) }) : null, jsxRuntimeExports.jsx(IconButton, { alt: "click to recenter", onClick: () => onRecenterButtonCLick(), "aria-label": "Recenter scatterplot view", children: jsxRuntimeExports.jsx(CenterFocusStrong, {}) })] });
158164
+ return jsxRuntimeExports.jsxs("div", { className: classes.tool, children: [visibleTools.pan && jsxRuntimeExports.jsx(IconTool, { alt: pointerIconAltText, onClick: () => setActiveTool(null), isActive: activeTool === null, children: jsxRuntimeExports.jsx(SvgNearMe, {}) }), visibleTools.selectLasso ? jsxRuntimeExports.jsx(IconTool, { alt: lassoIconAltText, onClick: () => setActiveTool(distEs6Exports$2.SELECTION_TYPE.POLYGON), isActive: activeTool === distEs6Exports$2.SELECTION_TYPE.POLYGON, children: jsxRuntimeExports.jsx(SvgSelectionLasso, {}) }) : null, visibleTools.recenter ? jsxRuntimeExports.jsx(IconButton, { alt: "click to recenter", onClick: () => onRecenterButtonCLick(), "aria-label": "Recenter scatterplot view", children: jsxRuntimeExports.jsx(CenterFocusStrong, {}) }) : null] });
158149
158165
  }
158150
158166
  const getCursorWithTool = () => "crosshair";
158151
158167
  const getCursor = (interactionState) => interactionState.isDragging ? "grabbing" : "default";
@@ -158380,7 +158396,7 @@ class AbstractSpatialOrScatterplot extends PureComponent {
158380
158396
  */
158381
158397
  render() {
158382
158398
  var _a3, _b2, _c, _d;
158383
- const { deckRef, viewState, uuid, hideTools, orbitAxis } = this.props;
158399
+ const { deckRef, viewState, uuid, hideTools, hideRecenter, orbitAxis } = this.props;
158384
158400
  const { gl, tool } = this.state;
158385
158401
  const layers = this.getLayers();
158386
158402
  const use3d = this.use3d();
@@ -158389,7 +158405,8 @@ class AbstractSpatialOrScatterplot extends PureComponent {
158389
158405
  const useDevicePixels = !use3d && (((_b2 = (_a3 = this.obsSegmentationsData) == null ? void 0 : _a3.shape) == null ? void 0 : _b2[0]) < 1e5 || ((_d = (_c = this.obsLocationsData) == null ? void 0 : _c.shape) == null ? void 0 : _d[1]) < 1e5);
158390
158406
  return jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx(ToolMenu, { activeTool: tool, setActiveTool: this.onToolChange, visibleTools: {
158391
158407
  pan: showPanTool && !hideTools,
158392
- selectLasso: showCellSelectionTools && !hideTools
158408
+ selectLasso: showCellSelectionTools && !hideTools,
158409
+ recenter: !hideRecenter
158393
158410
  }, recenterOnClick: this.recenter }), jsxRuntimeExports.jsx(DeckGL, { id: `deckgl-overlay-${uuid}`, ref: deckRef, views: [
158394
158411
  use3d ? new OrbitView({ id: "orbit", controller: true, orbitAxis }) : new OrthographicView({
158395
158412
  id: "ortho"
@@ -159341,21 +159358,14 @@ function GatingScatterplotOptions(props) {
159341
159358
  transformOptions
159342
159359
  } = props;
159343
159360
  const gatingScatterplotOptionsId = $bdb11010cef70236$export$f680877a34711e37();
159361
+ const gatingScatterplotGeneXId = $bdb11010cef70236$export$f680877a34711e37();
159362
+ const gatingScatterplotGeneYId = $bdb11010cef70236$export$f680877a34711e37();
159344
159363
  const { classes } = usePlotOptionsStyles();
159345
- const handleGeneSelectChange = (event) => {
159346
- const { options } = event.target;
159347
- const newValues = [];
159348
- for (let i2 = 0, l2 = options.length; i2 < l2; i2 += 1) {
159349
- if (options[i2].selected) {
159350
- newValues.push(options[i2].value);
159351
- }
159352
- }
159353
- if (newValues.length === 1 && gatingFeatureSelectionX && !gatingFeatureSelectionY && newValues[0] !== gatingFeatureSelectionX) {
159354
- setGatingFeatureSelectionY(newValues[0]);
159355
- } else if (newValues.length <= 2) {
159356
- setGatingFeatureSelectionX(newValues[0]);
159357
- setGatingFeatureSelectionY(newValues[1]);
159358
- }
159364
+ const handleSelectionX = (event) => {
159365
+ setGatingFeatureSelectionX(event.target.value === "" ? null : event.target.value);
159366
+ };
159367
+ const handleSelectionY = (event) => {
159368
+ setGatingFeatureSelectionY(event.target.value === "" ? null : event.target.value);
159359
159369
  };
159360
159370
  const handleTransformChange = (event) => {
159361
159371
  setGatingFeatureValueTransform(event.target.value === "" ? null : event.target.value);
@@ -159374,20 +159384,40 @@ function GatingScatterplotOptions(props) {
159374
159384
  return /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, /* @__PURE__ */ React__default.createElement(TableRow, null, /* @__PURE__ */ React__default.createElement(TableCell, { className: classes.labelCell, variant: "head", scope: "row" }, /* @__PURE__ */ React__default.createElement(
159375
159385
  "label",
159376
159386
  {
159377
- htmlFor: `scatterplot-gating-gene-select-${gatingScatterplotOptionsId}`
159387
+ htmlFor: `scatterplot-gating-gene-select-${gatingScatterplotGeneXId}`
159388
+ },
159389
+ capitalize$2(featureType),
159390
+ " along X"
159391
+ )), /* @__PURE__ */ React__default.createElement(TableCell, { className: classes.inputCell, variant: "body" }, /* @__PURE__ */ React__default.createElement(
159392
+ OptionSelect,
159393
+ {
159394
+ className: classes.select,
159395
+ value: gatingFeatureSelectionX ?? "",
159396
+ onChange: handleSelectionX,
159397
+ inputProps: {
159398
+ id: `scatterplot-gating-gene-select-${gatingScatterplotGeneXId}`
159399
+ }
159400
+ },
159401
+ /* @__PURE__ */ React__default.createElement("option", { value: "" }, "None"),
159402
+ geneSelectOptions.map((name2) => /* @__PURE__ */ React__default.createElement("option", { key: name2, value: name2 }, name2))
159403
+ ))), /* @__PURE__ */ React__default.createElement(TableRow, null, /* @__PURE__ */ React__default.createElement(TableCell, { className: classes.labelCell, variant: "head", scope: "row" }, /* @__PURE__ */ React__default.createElement(
159404
+ "label",
159405
+ {
159406
+ htmlFor: `scatterplot-gating-gene-select-${gatingScatterplotGeneYId}`
159378
159407
  },
159379
- capitalize$2(pluralize(featureType, geneSelectOptions == null ? void 0 : geneSelectOptions.length))
159408
+ capitalize$2(featureType),
159409
+ " along Y"
159380
159410
  )), /* @__PURE__ */ React__default.createElement(TableCell, { className: classes.inputCell, variant: "body" }, /* @__PURE__ */ React__default.createElement(
159381
159411
  OptionSelect,
159382
159412
  {
159383
- multiple: true,
159384
159413
  className: classes.select,
159385
- value: [gatingFeatureSelectionX, gatingFeatureSelectionY].filter((v) => v),
159386
- onChange: handleGeneSelectChange,
159414
+ value: gatingFeatureSelectionY ?? "",
159415
+ onChange: handleSelectionY,
159387
159416
  inputProps: {
159388
- id: `scatterplot-gating-gene-select-${gatingScatterplotOptionsId}`
159417
+ id: `scatterplot-gating-gene-select-${gatingScatterplotGeneYId}`
159389
159418
  }
159390
159419
  },
159420
+ /* @__PURE__ */ React__default.createElement("option", { value: "" }, "None"),
159391
159421
  geneSelectOptions.map((name2) => /* @__PURE__ */ React__default.createElement("option", { key: name2, value: name2 }, name2))
159392
159422
  ))), /* @__PURE__ */ React__default.createElement(TableRow, null, /* @__PURE__ */ React__default.createElement(TableCell, { className: classes.labelCell, variant: "head", scope: "row" }, /* @__PURE__ */ React__default.createElement(
159393
159423
  "label",
@@ -159419,7 +159449,7 @@ function GatingScatterplotOptions(props) {
159419
159449
  type: "number",
159420
159450
  onChange: handleTransformCoefficientChange,
159421
159451
  value: gatingFeatureValueTransformCoefficient,
159422
- slotProps: { input: { shrink: true } },
159452
+ slotProps: { inputLabel: { shrink: true } },
159423
159453
  id: `scatterplot-gating-transform-coefficient-${gatingScatterplotOptionsId}`
159424
159454
  }
159425
159455
  ))));
@@ -159499,6 +159529,14 @@ function GatingSubscriber(props) {
159499
159529
  COMPONENT_COORDINATION_TYPES[ViewType$1.GATING],
159500
159530
  coordinationScopes
159501
159531
  );
159532
+ const {
159533
+ embeddingZoom: initialZoom,
159534
+ embeddingTargetX: initialTargetX,
159535
+ embeddingTargetY: initialTargetY
159536
+ } = useInitialCoordination(
159537
+ COMPONENT_COORDINATION_TYPES[ViewType$1.GATING],
159538
+ coordinationScopes
159539
+ );
159502
159540
  const [width2, height2, deckRef] = useDeckCanvasSize();
159503
159541
  const title = useMemo(() => {
159504
159542
  if (titleOverride) {
@@ -159622,6 +159660,7 @@ function GatingSubscriber(props) {
159622
159660
  ]);
159623
159661
  const [dynamicCellRadius, setDynamicCellRadius] = useState(cellRadiusFixed);
159624
159662
  const [dynamicCellOpacity, setDynamicCellOpacity] = useState(cellOpacityFixed);
159663
+ const [originalViewState, setOriginalViewState] = useState(null);
159625
159664
  const mergedCellSets = useMemo(() => mergeObsSets(
159626
159665
  cellSets,
159627
159666
  additionalCellSets
@@ -159722,13 +159761,20 @@ function GatingSubscriber(props) {
159722
159761
  averageFillDensity
159723
159762
  );
159724
159763
  setDynamicCellOpacity(nextCellOpacityScale);
159725
- if (typeof targetX !== "number" || typeof targetY !== "number") {
159764
+ if (typeof initialTargetX !== "number" || typeof initialTargetY !== "number") {
159726
159765
  const newTargetX = xExtent[0] + xRange / 2;
159727
159766
  const newTargetY = yExtent[0] + yRange / 2;
159728
159767
  const newZoom = Math.log2(Math.min(width2 / xRange, height2 / yRange));
159729
- setTargetX(newTargetX);
159730
- setTargetY(-newTargetY);
159731
- setZoom(newZoom);
159768
+ const notYetInitialized = typeof targetX !== "number" || typeof targetY !== "number";
159769
+ const stillDefaultInitialized = targetX === newTargetX && targetY === -newTargetY;
159770
+ if (notYetInitialized || stillDefaultInitialized) {
159771
+ setTargetX(newTargetX);
159772
+ setTargetY(-newTargetY);
159773
+ setZoom(newZoom);
159774
+ }
159775
+ setOriginalViewState({ target: [newTargetX, -newTargetY, 0], zoom: newZoom });
159776
+ } else if (!originalViewState) {
159777
+ setOriginalViewState({ target: [initialTargetX, initialTargetY, 0], zoom: initialZoom });
159732
159778
  }
159733
159779
  }
159734
159780
  }, [
@@ -159740,6 +159786,8 @@ function GatingSubscriber(props) {
159740
159786
  width2,
159741
159787
  height2,
159742
159788
  zoom,
159789
+ initialTargetX,
159790
+ initialTargetY,
159743
159791
  averageFillDensity
159744
159792
  ]);
159745
159793
  const cellSelectionSet = useMemo(() => new Set(cellSelection), [cellSelection]);
@@ -159773,6 +159821,12 @@ function GatingSubscriber(props) {
159773
159821
  gatingFeatureSelectionY,
159774
159822
  obsType
159775
159823
  ]);
159824
+ const setViewState = ({ zoom: newZoom, target }) => {
159825
+ setZoom(newZoom);
159826
+ setTargetX(target[0]);
159827
+ setTargetY(target[1]);
159828
+ setTargetZ(target[2] || 0);
159829
+ };
159776
159830
  return /* @__PURE__ */ React__default.createElement(
159777
159831
  TitleInfo,
159778
159832
  {
@@ -159848,13 +159902,10 @@ function GatingSubscriber(props) {
159848
159902
  uuid,
159849
159903
  theme,
159850
159904
  hideTools: !(gatingFeatureSelectionX && gatingFeatureSelectionY),
159905
+ hideRecenter: !(gatingFeatureSelectionX && gatingFeatureSelectionY),
159851
159906
  viewState: { zoom, target: [targetX, targetY, targetZ] },
159852
- setViewState: ({ zoom: newZoom, target }) => {
159853
- setZoom(newZoom);
159854
- setTargetX(target[0]);
159855
- setTargetY(target[1]);
159856
- setTargetZ(target[2] || 0);
159857
- },
159907
+ setViewState,
159908
+ originalViewState,
159858
159909
  obsEmbeddingIndex: obsIndex,
159859
159910
  obsEmbedding: obsXY,
159860
159911
  cellFilter,
@@ -159878,7 +159929,8 @@ function GatingSubscriber(props) {
159878
159929
  },
159879
159930
  updateViewInfo: setComponentViewInfo,
159880
159931
  getExpressionValue,
159881
- getCellIsSelected
159932
+ getCellIsSelected,
159933
+ embeddingPointsVisible: true
159882
159934
  }
159883
159935
  ),
159884
159936
  !disableTooltip && /* @__PURE__ */ React__default.createElement(
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { G } from "./index-B4FTJe_L.js";
1
+ import { G } from "./index-BZoxwGBy.js";
2
2
  export {
3
3
  G as GatingSubscriber
4
4
  };
@@ -1,4 +1,4 @@
1
- import { B as BaseDecoder } from "./index-B4FTJe_L.js";
1
+ import { B as BaseDecoder } from "./index-BZoxwGBy.js";
2
2
  const dctZigZag = new Int32Array([
3
3
  0,
4
4
  1,
@@ -1,5 +1,5 @@
1
1
  import { i as inflate_1 } from "./pako.esm-SxljTded.js";
2
- import { g as getDefaultExportFromCjs, B as BaseDecoder } from "./index-B4FTJe_L.js";
2
+ import { g as getDefaultExportFromCjs, B as BaseDecoder } from "./index-BZoxwGBy.js";
3
3
  const LercParameters = {
4
4
  AddCompression: 1
5
5
  };
@@ -1,4 +1,4 @@
1
- import { B as BaseDecoder } from "./index-B4FTJe_L.js";
1
+ import { B as BaseDecoder } from "./index-BZoxwGBy.js";
2
2
  const MIN_BITS = 9;
3
3
  const CLEAR_CODE = 256;
4
4
  const EOI_CODE = 257;
@@ -1,4 +1,4 @@
1
- import { B as BaseDecoder } from "./index-B4FTJe_L.js";
1
+ import { B as BaseDecoder } from "./index-BZoxwGBy.js";
2
2
  class PackbitsDecoder extends BaseDecoder {
3
3
  decodeBlock(buffer) {
4
4
  const dataView = new DataView(buffer);
@@ -1,4 +1,4 @@
1
- import { B as BaseDecoder } from "./index-B4FTJe_L.js";
1
+ import { B as BaseDecoder } from "./index-BZoxwGBy.js";
2
2
  class RawDecoder extends BaseDecoder {
3
3
  decodeBlock(buffer) {
4
4
  return buffer;
@@ -1,4 +1,4 @@
1
- import { B as BaseDecoder } from "./index-B4FTJe_L.js";
1
+ import { B as BaseDecoder } from "./index-BZoxwGBy.js";
2
2
  class WebImageDecoder extends BaseDecoder {
3
3
  constructor() {
4
4
  super();
@@ -1 +1 @@
1
- {"version":3,"file":"GatingScatterplotOptions.d.ts","sourceRoot":"","sources":["../src/GatingScatterplotOptions.js"],"names":[],"mappings":"AAMA,0EAoIC"}
1
+ {"version":3,"file":"GatingScatterplotOptions.d.ts","sourceRoot":"","sources":["../src/GatingScatterplotOptions.js"],"names":[],"mappings":"AAMA,0EAwJC"}
@@ -1,32 +1,21 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import React from 'react';
3
3
  import { useId } from 'react-aria';
4
4
  import { TableCell, TableRow, TextField } from '@vitessce/styles';
5
5
  import { usePlotOptionsStyles, OptionSelect } from '@vitessce/vit-s';
6
- import { capitalize, pluralize as plur } from '@vitessce/utils';
6
+ import { capitalize } from '@vitessce/utils';
7
7
  export default function GatingScatterplotOptions(props) {
8
8
  const { featureType, gatingFeatureSelectionX, setGatingFeatureSelectionX, gatingFeatureSelectionY, setGatingFeatureSelectionY, gatingFeatureValueTransform, setGatingFeatureValueTransform, gatingFeatureValueTransformCoefficient, setGatingFeatureValueTransformCoefficient, geneSelectOptions, transformOptions, } = props;
9
9
  const gatingScatterplotOptionsId = useId();
10
+ const gatingScatterplotGeneXId = useId();
11
+ const gatingScatterplotGeneYId = useId();
10
12
  const { classes } = usePlotOptionsStyles();
11
13
  // Handlers for custom option field changes.
12
- const handleGeneSelectChange = (event) => {
13
- const { options } = event.target;
14
- const newValues = [];
15
- for (let i = 0, l = options.length; i < l; i += 1) {
16
- if (options[i].selected) {
17
- newValues.push(options[i].value);
18
- }
19
- }
20
- if (newValues.length === 1
21
- && gatingFeatureSelectionX
22
- && !gatingFeatureSelectionY
23
- && newValues[0] !== gatingFeatureSelectionX) {
24
- setGatingFeatureSelectionY(newValues[0]);
25
- }
26
- else if (newValues.length <= 2) {
27
- setGatingFeatureSelectionX(newValues[0]);
28
- setGatingFeatureSelectionY(newValues[1]);
29
- }
14
+ const handleSelectionX = (event) => {
15
+ setGatingFeatureSelectionX(event.target.value === '' ? null : event.target.value);
16
+ };
17
+ const handleSelectionY = (event) => {
18
+ setGatingFeatureSelectionY(event.target.value === '' ? null : event.target.value);
30
19
  };
31
20
  const handleTransformChange = (event) => {
32
21
  setGatingFeatureValueTransform(event.target.value === '' ? null : event.target.value);
@@ -45,9 +34,11 @@ export default function GatingScatterplotOptions(props) {
45
34
  }
46
35
  }
47
36
  };
48
- return (_jsxs(_Fragment, { children: [_jsxs(TableRow, { children: [_jsx(TableCell, { className: classes.labelCell, variant: "head", scope: "row", children: _jsx("label", { htmlFor: `scatterplot-gating-gene-select-${gatingScatterplotOptionsId}`, children: capitalize(plur(featureType, geneSelectOptions?.length)) }) }), _jsx(TableCell, { className: classes.inputCell, variant: "body", children: _jsx(OptionSelect, { multiple: true, className: classes.select, value: [gatingFeatureSelectionX, gatingFeatureSelectionY].filter(v => v), onChange: handleGeneSelectChange, inputProps: {
49
- id: `scatterplot-gating-gene-select-${gatingScatterplotOptionsId}`,
50
- }, children: geneSelectOptions.map(name => (_jsx("option", { value: name, children: name }, name))) }) })] }), _jsxs(TableRow, { children: [_jsx(TableCell, { className: classes.labelCell, variant: "head", scope: "row", children: _jsx("label", { htmlFor: `scatterplot-gating-transform-select-${gatingScatterplotOptionsId}`, children: "Transform" }) }), _jsx(TableCell, { className: classes.inputCell, variant: "body", children: _jsx(OptionSelect, { className: classes.select, value: gatingFeatureValueTransform === null ? '' : gatingFeatureValueTransform, onChange: handleTransformChange, inputProps: {
37
+ return (_jsxs(_Fragment, { children: [_jsxs(TableRow, { children: [_jsx(TableCell, { className: classes.labelCell, variant: "head", scope: "row", children: _jsxs("label", { htmlFor: `scatterplot-gating-gene-select-${gatingScatterplotGeneXId}`, children: [capitalize(featureType), " along X"] }) }), _jsx(TableCell, { className: classes.inputCell, variant: "body", children: _jsxs(OptionSelect, { className: classes.select, value: gatingFeatureSelectionX ?? '', onChange: handleSelectionX, inputProps: {
38
+ id: `scatterplot-gating-gene-select-${gatingScatterplotGeneXId}`,
39
+ }, children: [_jsx("option", { value: "", children: "None" }), geneSelectOptions.map(name => (_jsx("option", { value: name, children: name }, name)))] }) })] }), _jsxs(TableRow, { children: [_jsx(TableCell, { className: classes.labelCell, variant: "head", scope: "row", children: _jsxs("label", { htmlFor: `scatterplot-gating-gene-select-${gatingScatterplotGeneYId}`, children: [capitalize(featureType), " along Y"] }) }), _jsx(TableCell, { className: classes.inputCell, variant: "body", children: _jsxs(OptionSelect, { className: classes.select, value: gatingFeatureSelectionY ?? '', onChange: handleSelectionY, inputProps: {
40
+ id: `scatterplot-gating-gene-select-${gatingScatterplotGeneYId}`,
41
+ }, children: [_jsx("option", { value: "", children: "None" }), geneSelectOptions.map(name => (_jsx("option", { value: name, children: name }, name)))] }) })] }), _jsxs(TableRow, { children: [_jsx(TableCell, { className: classes.labelCell, variant: "head", scope: "row", children: _jsx("label", { htmlFor: `scatterplot-gating-transform-select-${gatingScatterplotOptionsId}`, children: "Transform" }) }), _jsx(TableCell, { className: classes.inputCell, variant: "body", children: _jsx(OptionSelect, { className: classes.select, value: gatingFeatureValueTransform === null ? '' : gatingFeatureValueTransform, onChange: handleTransformChange, inputProps: {
51
42
  id: `scatterplot-gating-transform-select-${gatingScatterplotOptionsId}`,
52
- }, children: transformOptions.map(opt => (_jsx("option", { value: opt.value === null ? '' : opt.value, children: opt.name }, opt.name))) }) })] }), _jsxs(TableRow, { children: [_jsx(TableCell, { className: classes.labelCell, variant: "head", scope: "row", children: _jsx("label", { htmlFor: `scatterplot-gating-transform-coefficient-${gatingScatterplotOptionsId}`, children: "Transform Coefficient" }) }), _jsx(TableCell, { className: classes.inputCell, variant: "body", children: _jsx(TextField, { label: "Transform Coefficient", type: "number", onChange: handleTransformCoefficientChange, value: gatingFeatureValueTransformCoefficient, slotProps: { input: { shrink: true } }, id: `scatterplot-gating-transform-coefficient-${gatingScatterplotOptionsId}` }) })] })] }));
43
+ }, children: transformOptions.map(opt => (_jsx("option", { value: opt.value === null ? '' : opt.value, children: opt.name }, opt.name))) }) })] }), _jsxs(TableRow, { children: [_jsx(TableCell, { className: classes.labelCell, variant: "head", scope: "row", children: _jsx("label", { htmlFor: `scatterplot-gating-transform-coefficient-${gatingScatterplotOptionsId}`, children: "Transform Coefficient" }) }), _jsx(TableCell, { className: classes.inputCell, variant: "body", children: _jsx(TextField, { label: "Transform Coefficient", type: "number", onChange: handleTransformCoefficientChange, value: gatingFeatureValueTransformCoefficient, slotProps: { inputLabel: { shrink: true } }, id: `scatterplot-gating-transform-coefficient-${gatingScatterplotOptionsId}` }) })] })] }));
53
44
  }
@@ -1 +1 @@
1
- {"version":3,"file":"GatingSubscriber.d.ts","sourceRoot":"","sources":["../src/GatingSubscriber.js"],"names":[],"mappings":"AAoCA;;;;;;;;;;;;KAYK;AACL,wCAVK;IAAsB,IAAI,EAAlB,MAAM;IACQ,KAAK,EAAnB,MAAM;IACQ,kBAAkB,EAAhC,MAAM;IAES,cAAc,EAA7B,OAAO;IACS,mBAAmB;IAErB,kBAAkB,EAAhC,MAAM;CAEhB,eAobF"}
1
+ {"version":3,"file":"GatingSubscriber.d.ts","sourceRoot":"","sources":["../src/GatingSubscriber.js"],"names":[],"mappings":"AAqCA;;;;;;;;;;;;KAYK;AACL,wCAVK;IAAsB,IAAI,EAAlB,MAAM;IACQ,KAAK,EAAnB,MAAM;IACQ,kBAAkB,EAAhC,MAAM;IAES,cAAc,EAA7B,OAAO;IACS,mBAAmB;IAErB,kBAAkB,EAAhC,MAAM;CAEhB,eA6cF"}
@@ -3,7 +3,7 @@ import React, { useState, useEffect, useCallback, useMemo, } from 'react';
3
3
  import { extent } from 'd3-array';
4
4
  import { isEqual } from 'lodash-es';
5
5
  import { pluralize as plur, capitalize, commaNumber, getValueTransformFunction, VALUE_TRANSFORM_OPTIONS, } from '@vitessce/utils';
6
- import { TitleInfo, useDeckCanvasSize, useReady, useUrls, useUint8FeatureSelection, useExpressionValueGetter, useObsSetsData, useFeatureSelection, useObsFeatureMatrixIndices, useCoordination, useLoaders, useSetComponentHover, useSetComponentViewInfo, useCoordinationScopes, } from '@vitessce/vit-s';
6
+ import { TitleInfo, useDeckCanvasSize, useReady, useUrls, useUint8FeatureSelection, useExpressionValueGetter, useObsSetsData, useFeatureSelection, useObsFeatureMatrixIndices, useCoordination, useInitialCoordination, useLoaders, useSetComponentHover, useSetComponentViewInfo, useCoordinationScopes, } from '@vitessce/vit-s';
7
7
  import { getCellSetPolygons, mergeObsSets, setObsSelection, getCellColors, } from '@vitessce/sets-utils';
8
8
  import { Scatterplot, ScatterplotTooltipSubscriber, ScatterplotOptions, getPointSizeDevicePixels, getPointOpacity, EmptyMessage, } from '@vitessce/scatterplot';
9
9
  import { ViewType, COMPONENT_COORDINATION_TYPES, ViewHelpMapping } from '@vitessce/constants-internal';
@@ -31,6 +31,7 @@ export function GatingSubscriber(props) {
31
31
  const setComponentViewInfo = useSetComponentViewInfo(uuid);
32
32
  // Get "props" from the coordination space.
33
33
  const [{ dataset, obsType, featureType, featureValueType, embeddingZoom: zoom, embeddingTargetX: targetX, embeddingTargetY: targetY, embeddingTargetZ: targetZ, obsFilter: cellFilter, obsHighlight: cellHighlight, obsSetSelection: cellSetSelection, obsSetColor: cellSetColor, obsColorEncoding: cellColorEncoding, additionalObsSets: additionalCellSets, embeddingObsSetPolygonsVisible: cellSetPolygonsVisible, embeddingObsSetLabelsVisible: cellSetLabelsVisible, embeddingObsSetLabelSize: cellSetLabelSize, embeddingObsRadius: cellRadiusFixed, embeddingObsRadiusMode: cellRadiusMode, embeddingObsOpacity: cellOpacityFixed, embeddingObsOpacityMode: cellOpacityMode, featureValueColormap: geneExpressionColormap, featureValueColormapRange: geneExpressionColormapRange, featureSelection: gatingFeatureSelectionColor, featureValueTransform, featureValueTransformCoefficient, gatingFeatureSelectionX, gatingFeatureSelectionY, }, { setEmbeddingZoom: setZoom, setEmbeddingTargetX: setTargetX, setEmbeddingTargetY: setTargetY, setEmbeddingTargetZ: setTargetZ, setObsFilter: setCellFilter, setObsSetSelection: setCellSetSelection, setObsHighlight: setCellHighlight, setObsSetColor: setCellSetColor, setObsColorEncoding: setCellColorEncoding, setAdditionalObsSets: setAdditionalCellSets, setEmbeddingObsSetPolygonsVisible: setCellSetPolygonsVisible, setEmbeddingObsSetLabelsVisible: setCellSetLabelsVisible, setEmbeddingObsSetLabelSize: setCellSetLabelSize, setEmbeddingObsRadius: setCellRadiusFixed, setEmbeddingObsRadiusMode: setCellRadiusMode, setEmbeddingObsOpacity: setCellOpacityFixed, setEmbeddingObsOpacityMode: setCellOpacityMode, setFeatureValueColormap: setGeneExpressionColormap, setFeatureValueColormapRange: setGeneExpressionColormapRange, setFeatureValueTransform, setFeatureValueTransformCoefficient, setGatingFeatureSelectionX, setGatingFeatureSelectionY, }] = useCoordination(COMPONENT_COORDINATION_TYPES[ViewType.GATING], coordinationScopes);
34
+ const { embeddingZoom: initialZoom, embeddingTargetX: initialTargetX, embeddingTargetY: initialTargetY, } = useInitialCoordination(COMPONENT_COORDINATION_TYPES[ViewType.GATING], coordinationScopes);
34
35
  const [width, height, deckRef] = useDeckCanvasSize();
35
36
  const title = useMemo(() => {
36
37
  if (titleOverride) {
@@ -105,6 +106,7 @@ export function GatingSubscriber(props) {
105
106
  ]);
106
107
  const [dynamicCellRadius, setDynamicCellRadius] = useState(cellRadiusFixed);
107
108
  const [dynamicCellOpacity, setDynamicCellOpacity] = useState(cellOpacityFixed);
109
+ const [originalViewState, setOriginalViewState] = useState(null);
108
110
  const mergedCellSets = useMemo(() => mergeObsSets(cellSets, additionalCellSets), [cellSets, additionalCellSets]);
109
111
  const setCellSelectionProp = useCallback((v) => {
110
112
  setObsSelection(v, additionalCellSets, cellSetColor, setCellSetSelection, setAdditionalCellSets, setCellSetColor, setCellColorEncoding);
@@ -165,19 +167,29 @@ export function GatingSubscriber(props) {
165
167
  setDynamicCellRadius(pointSizeDevicePixels);
166
168
  const nextCellOpacityScale = getPointOpacity(zoom, xRange, yRange, width, height, numCells, averageFillDensity);
167
169
  setDynamicCellOpacity(nextCellOpacityScale);
168
- if (typeof targetX !== 'number' || typeof targetY !== 'number') {
170
+ if (typeof initialTargetX !== 'number' || typeof initialTargetY !== 'number') {
169
171
  const newTargetX = xExtent[0] + xRange / 2;
170
172
  const newTargetY = yExtent[0] + yRange / 2;
171
173
  const newZoom = Math.log2(Math.min(width / xRange, height / yRange));
172
- setTargetX(newTargetX);
173
- // Graphics rendering has the y-axis going south so we need to multiply by negative one.
174
- setTargetY(-newTargetY);
175
- setZoom(newZoom);
174
+ const notYetInitialized = (typeof targetX !== 'number' || typeof targetY !== 'number');
175
+ const stillDefaultInitialized = (targetX === newTargetX && targetY === -newTargetY);
176
+ if (notYetInitialized || stillDefaultInitialized) {
177
+ setTargetX(newTargetX);
178
+ // Graphics rendering has the y-axis going south so we need to multiply by negative one.
179
+ setTargetY(-newTargetY);
180
+ setZoom(newZoom);
181
+ }
182
+ setOriginalViewState({ target: [newTargetX, -newTargetY, 0], zoom: newZoom });
183
+ }
184
+ else if (!originalViewState) {
185
+ // originalViewState has not yet been set and
186
+ // the view config defined an initial viewState.
187
+ setOriginalViewState({ target: [initialTargetX, initialTargetY, 0], zoom: initialZoom });
176
188
  }
177
189
  }
178
190
  // eslint-disable-next-line react-hooks/exhaustive-deps
179
191
  }, [xRange, yRange, xExtent, yExtent, numCells,
180
- width, height, zoom, averageFillDensity]);
192
+ width, height, zoom, initialTargetX, initialTargetY, averageFillDensity]);
181
193
  const cellSelectionSet = useMemo(() => new Set(cellSelection), [cellSelection]);
182
194
  const getCellIsSelected = useCallback((object, { index }) => ((cellSelectionSet || new Set([])).has(obsIndex[index]) ? 1.0 : 0.0), [cellSelectionSet, obsIndex]);
183
195
  const cellRadius = (cellRadiusMode === 'manual' ? cellRadiusFixed : dynamicCellRadius);
@@ -204,17 +216,18 @@ export function GatingSubscriber(props) {
204
216
  }, [obsXY, obsIndex, featureValueTransform,
205
217
  gatingFeatureSelectionX, gatingFeatureSelectionY, obsType,
206
218
  ]);
219
+ const setViewState = ({ zoom: newZoom, target }) => {
220
+ setZoom(newZoom);
221
+ setTargetX(target[0]);
222
+ setTargetY(target[1]);
223
+ setTargetZ(target[2] || 0);
224
+ };
207
225
  return (_jsxs(TitleInfo, { title: title, info: `${commaNumber(cellsCount)} ${plur(obsType, cellsCount)}`, closeButtonVisible: closeButtonVisible, downloadButtonVisible: downloadButtonVisible, removeGridComponent: removeGridComponent, urls: urls, theme: theme, isReady: isReady, helpText: helpText, errors: errors, options: (_jsx(ScatterplotOptions, { observationsLabel: obsType, cellRadius: cellRadiusFixed, setCellRadius: setCellRadiusFixed, cellRadiusMode: cellRadiusMode, setCellRadiusMode: setCellRadiusMode, cellOpacity: cellOpacityFixed, setCellOpacity: setCellOpacityFixed, cellOpacityMode: cellOpacityMode, setCellOpacityMode: setCellOpacityMode, cellSetLabelsVisible: cellSetLabelsVisible, setCellSetLabelsVisible: setCellSetLabelsVisible, cellSetLabelSize: cellSetLabelSize, setCellSetLabelSize: setCellSetLabelSize, cellSetPolygonsVisible: cellSetPolygonsVisible, setCellSetPolygonsVisible: setCellSetPolygonsVisible, cellColorEncoding: cellColorEncoding, setCellColorEncoding: setCellColorEncoding, geneExpressionColormap: geneExpressionColormap, setGeneExpressionColormap: setGeneExpressionColormap, geneExpressionColormapRange: geneExpressionColormapRange, setGeneExpressionColormapRange: setGeneExpressionColormapRange, children: _jsx(GatingScatterplotOptions, { featureType: featureType, gatingFeatureSelectionX: gatingFeatureSelectionX, setGatingFeatureSelectionX: setGatingFeatureSelectionX, gatingFeatureSelectionY: gatingFeatureSelectionY, setGatingFeatureSelectionY: setGatingFeatureSelectionY, gatingFeatureValueTransform: featureValueTransform, setGatingFeatureValueTransform: (newValue) => {
208
226
  setFeatureValueTransform(newValue);
209
227
  setTargetX(null);
210
228
  setTargetY(null);
211
229
  setZoom(null);
212
- }, gatingFeatureValueTransformCoefficient: featureValueTransformCoefficient, setGatingFeatureValueTransformCoefficient: setFeatureValueTransformCoefficient, geneSelectOptions: featureIndex, transformOptions: VALUE_TRANSFORM_OPTIONS }) })), children: [_jsx(EmptyMessage, { visible: !(gatingFeatureSelectionX && gatingFeatureSelectionY), message: `Select two ${plur(featureType, 2)} in the plot settings.` }), _jsx(Scatterplot, { ref: deckRef, uuid: uuid, theme: theme, hideTools: !(gatingFeatureSelectionX && gatingFeatureSelectionY), viewState: { zoom, target: [targetX, targetY, targetZ] }, setViewState: ({ zoom: newZoom, target }) => {
213
- setZoom(newZoom);
214
- setTargetX(target[0]);
215
- setTargetY(target[1]);
216
- setTargetZ(target[2] || 0);
217
- }, obsEmbeddingIndex: obsIndex, obsEmbedding: obsXY, cellFilter: cellFilter, cellSelection: cellSelection, cellHighlight: cellHighlight, cellColors: cellColors, cellSetPolygons: cellSetPolygons, cellSetLabelSize: cellSetLabelSize, cellSetLabelsVisible: cellSetLabelsVisible, cellSetPolygonsVisible: cellSetPolygonsVisible, setCellFilter: setCellFilter, setCellSelection: setCellSelectionProp, setCellHighlight: setCellHighlight, cellRadius: cellRadius, cellOpacity: cellOpacity, cellColorEncoding: cellColorEncoding, geneExpressionColormap: geneExpressionColormap, geneExpressionColormapRange: geneExpressionColormapRange, setComponentHover: () => {
230
+ }, gatingFeatureValueTransformCoefficient: featureValueTransformCoefficient, setGatingFeatureValueTransformCoefficient: setFeatureValueTransformCoefficient, geneSelectOptions: featureIndex, transformOptions: VALUE_TRANSFORM_OPTIONS }) })), children: [_jsx(EmptyMessage, { visible: !(gatingFeatureSelectionX && gatingFeatureSelectionY), message: `Select two ${plur(featureType, 2)} in the plot settings.` }), _jsx(Scatterplot, { ref: deckRef, uuid: uuid, theme: theme, hideTools: !(gatingFeatureSelectionX && gatingFeatureSelectionY), hideRecenter: !(gatingFeatureSelectionX && gatingFeatureSelectionY), viewState: { zoom, target: [targetX, targetY, targetZ] }, setViewState: setViewState, originalViewState: originalViewState, obsEmbeddingIndex: obsIndex, obsEmbedding: obsXY, cellFilter: cellFilter, cellSelection: cellSelection, cellHighlight: cellHighlight, cellColors: cellColors, cellSetPolygons: cellSetPolygons, cellSetLabelSize: cellSetLabelSize, cellSetLabelsVisible: cellSetLabelsVisible, cellSetPolygonsVisible: cellSetPolygonsVisible, setCellFilter: setCellFilter, setCellSelection: setCellSelectionProp, setCellHighlight: setCellHighlight, cellRadius: cellRadius, cellOpacity: cellOpacity, cellColorEncoding: cellColorEncoding, geneExpressionColormap: geneExpressionColormap, geneExpressionColormapRange: geneExpressionColormapRange, setComponentHover: () => {
218
231
  setComponentHover(uuid);
219
- }, updateViewInfo: setComponentViewInfo, getExpressionValue: getExpressionValue, getCellIsSelected: getCellIsSelected }), !disableTooltip && (_jsx(ScatterplotTooltipSubscriber, { parentUuid: uuid, obsHighlight: cellHighlight, width: width, height: height, getObsInfo: getObsInfo }))] }));
232
+ }, updateViewInfo: setComponentViewInfo, getExpressionValue: getExpressionValue, getCellIsSelected: getCellIsSelected, embeddingPointsVisible: true }), !disableTooltip && (_jsx(ScatterplotTooltipSubscriber, { parentUuid: uuid, obsHighlight: cellHighlight, width: width, height: height, getObsInfo: getObsInfo }))] }));
220
233
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitessce/scatterplot-gating",
3
- "version": "3.8.0",
3
+ "version": "3.8.2",
4
4
  "author": "HIDIVE Lab at HMS",
5
5
  "homepage": "http://vitessce.io",
6
6
  "repository": {
@@ -19,12 +19,12 @@
19
19
  "d3-array": "^2.4.0",
20
20
  "lodash-es": "^4.17.21",
21
21
  "react-aria": "^3.28.0",
22
- "@vitessce/styles": "3.8.0",
23
- "@vitessce/constants-internal": "3.8.0",
24
- "@vitessce/scatterplot": "3.8.0",
25
- "@vitessce/sets-utils": "3.8.0",
26
- "@vitessce/utils": "3.8.0",
27
- "@vitessce/vit-s": "3.8.0"
22
+ "@vitessce/styles": "3.8.2",
23
+ "@vitessce/constants-internal": "3.8.2",
24
+ "@vitessce/scatterplot": "3.8.2",
25
+ "@vitessce/sets-utils": "3.8.2",
26
+ "@vitessce/utils": "3.8.2",
27
+ "@vitessce/vit-s": "3.8.2"
28
28
  },
29
29
  "devDependencies": {
30
30
  "react": "18.3.1",
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { useId } from 'react-aria';
3
3
  import { TableCell, TableRow, TextField } from '@vitessce/styles';
4
4
  import { usePlotOptionsStyles, OptionSelect } from '@vitessce/vit-s';
5
- import { capitalize, pluralize as plur } from '@vitessce/utils';
5
+ import { capitalize } from '@vitessce/utils';
6
6
 
7
7
  export default function GatingScatterplotOptions(props) {
8
8
  const {
@@ -20,28 +20,18 @@ export default function GatingScatterplotOptions(props) {
20
20
  } = props;
21
21
 
22
22
  const gatingScatterplotOptionsId = useId();
23
+ const gatingScatterplotGeneXId = useId();
24
+ const gatingScatterplotGeneYId = useId();
23
25
 
24
26
  const { classes } = usePlotOptionsStyles();
25
27
 
26
28
  // Handlers for custom option field changes.
27
- const handleGeneSelectChange = (event) => {
28
- const { options } = event.target;
29
- const newValues = [];
30
- for (let i = 0, l = options.length; i < l; i += 1) {
31
- if (options[i].selected) {
32
- newValues.push(options[i].value);
33
- }
34
- }
29
+ const handleSelectionX = (event) => {
30
+ setGatingFeatureSelectionX(event.target.value === '' ? null : event.target.value);
31
+ };
35
32
 
36
- if (newValues.length === 1
37
- && gatingFeatureSelectionX
38
- && !gatingFeatureSelectionY
39
- && newValues[0] !== gatingFeatureSelectionX) {
40
- setGatingFeatureSelectionY(newValues[0]);
41
- } else if (newValues.length <= 2) {
42
- setGatingFeatureSelectionX(newValues[0]);
43
- setGatingFeatureSelectionY(newValues[1]);
44
- }
33
+ const handleSelectionY = (event) => {
34
+ setGatingFeatureSelectionY(event.target.value === '' ? null : event.target.value);
45
35
  };
46
36
 
47
37
  const handleTransformChange = (event) => {
@@ -67,21 +57,51 @@ export default function GatingScatterplotOptions(props) {
67
57
  <TableRow>
68
58
  <TableCell className={classes.labelCell} variant="head" scope="row">
69
59
  <label
70
- htmlFor={`scatterplot-gating-gene-select-${gatingScatterplotOptionsId}`}
60
+ htmlFor={`scatterplot-gating-gene-select-${gatingScatterplotGeneXId}`}
61
+ >
62
+ {capitalize(featureType)} along X
63
+ </label>
64
+ </TableCell>
65
+ <TableCell className={classes.inputCell} variant="body">
66
+ <OptionSelect
67
+ className={classes.select}
68
+ value={gatingFeatureSelectionX ?? ''}
69
+ onChange={handleSelectionX}
70
+ inputProps={{
71
+ id: `scatterplot-gating-gene-select-${gatingScatterplotGeneXId}`,
72
+ }}
73
+ >
74
+ <option value="">
75
+ None
76
+ </option>
77
+ {geneSelectOptions.map(name => (
78
+ <option key={name} value={name}>
79
+ {name}
80
+ </option>
81
+ ))}
82
+ </OptionSelect>
83
+ </TableCell>
84
+ </TableRow>
85
+ <TableRow>
86
+ <TableCell className={classes.labelCell} variant="head" scope="row">
87
+ <label
88
+ htmlFor={`scatterplot-gating-gene-select-${gatingScatterplotGeneYId}`}
71
89
  >
72
- {capitalize(plur(featureType, geneSelectOptions?.length))}
90
+ {capitalize(featureType)} along Y
73
91
  </label>
74
92
  </TableCell>
75
93
  <TableCell className={classes.inputCell} variant="body">
76
94
  <OptionSelect
77
- multiple
78
95
  className={classes.select}
79
- value={[gatingFeatureSelectionX, gatingFeatureSelectionY].filter(v => v)}
80
- onChange={handleGeneSelectChange}
96
+ value={gatingFeatureSelectionY ?? ''}
97
+ onChange={handleSelectionY}
81
98
  inputProps={{
82
- id: `scatterplot-gating-gene-select-${gatingScatterplotOptionsId}`,
99
+ id: `scatterplot-gating-gene-select-${gatingScatterplotGeneYId}`,
83
100
  }}
84
101
  >
102
+ <option value="">
103
+ None
104
+ </option>
85
105
  {geneSelectOptions.map(name => (
86
106
  <option key={name} value={name}>
87
107
  {name}
@@ -129,7 +149,7 @@ export default function GatingScatterplotOptions(props) {
129
149
  type="number"
130
150
  onChange={handleTransformCoefficientChange}
131
151
  value={gatingFeatureValueTransformCoefficient}
132
- slotProps={{ input: { shrink: true } }}
152
+ slotProps={{ inputLabel: { shrink: true } }}
133
153
  id={`scatterplot-gating-transform-coefficient-${gatingScatterplotOptionsId}`}
134
154
  />
135
155
  </TableCell>
@@ -17,6 +17,7 @@ import {
17
17
  useFeatureSelection,
18
18
  useObsFeatureMatrixIndices,
19
19
  useCoordination,
20
+ useInitialCoordination,
20
21
  useLoaders,
21
22
  useSetComponentHover,
22
23
  useSetComponentViewInfo,
@@ -126,6 +127,14 @@ export function GatingSubscriber(props) {
126
127
  coordinationScopes,
127
128
  );
128
129
 
130
+ const {
131
+ embeddingZoom: initialZoom,
132
+ embeddingTargetX: initialTargetX,
133
+ embeddingTargetY: initialTargetY,
134
+ } = useInitialCoordination(
135
+ COMPONENT_COORDINATION_TYPES[ViewType.GATING], coordinationScopes,
136
+ );
137
+
129
138
  const [width, height, deckRef] = useDeckCanvasSize();
130
139
 
131
140
  const title = useMemo(() => {
@@ -244,6 +253,8 @@ export function GatingSubscriber(props) {
244
253
  const [dynamicCellRadius, setDynamicCellRadius] = useState(cellRadiusFixed);
245
254
  const [dynamicCellOpacity, setDynamicCellOpacity] = useState(cellOpacityFixed);
246
255
 
256
+ const [originalViewState, setOriginalViewState] = useState(null);
257
+
247
258
  const mergedCellSets = useMemo(() => mergeObsSets(
248
259
  cellSets, additionalCellSets,
249
260
  ), [cellSets, additionalCellSets]);
@@ -323,19 +334,28 @@ export function GatingSubscriber(props) {
323
334
  );
324
335
  setDynamicCellOpacity(nextCellOpacityScale);
325
336
 
326
- if (typeof targetX !== 'number' || typeof targetY !== 'number') {
337
+ if (typeof initialTargetX !== 'number' || typeof initialTargetY !== 'number') {
327
338
  const newTargetX = xExtent[0] + xRange / 2;
328
339
  const newTargetY = yExtent[0] + yRange / 2;
329
340
  const newZoom = Math.log2(Math.min(width / xRange, height / yRange));
330
- setTargetX(newTargetX);
331
- // Graphics rendering has the y-axis going south so we need to multiply by negative one.
332
- setTargetY(-newTargetY);
333
- setZoom(newZoom);
341
+ const notYetInitialized = (typeof targetX !== 'number' || typeof targetY !== 'number');
342
+ const stillDefaultInitialized = (targetX === newTargetX && targetY === -newTargetY);
343
+ if (notYetInitialized || stillDefaultInitialized) {
344
+ setTargetX(newTargetX);
345
+ // Graphics rendering has the y-axis going south so we need to multiply by negative one.
346
+ setTargetY(-newTargetY);
347
+ setZoom(newZoom);
348
+ }
349
+ setOriginalViewState({ target: [newTargetX, -newTargetY, 0], zoom: newZoom });
350
+ } else if (!originalViewState) {
351
+ // originalViewState has not yet been set and
352
+ // the view config defined an initial viewState.
353
+ setOriginalViewState({ target: [initialTargetX, initialTargetY, 0], zoom: initialZoom });
334
354
  }
335
355
  }
336
356
  // eslint-disable-next-line react-hooks/exhaustive-deps
337
357
  }, [xRange, yRange, xExtent, yExtent, numCells,
338
- width, height, zoom, averageFillDensity]);
358
+ width, height, zoom, initialTargetX, initialTargetY, averageFillDensity]);
339
359
 
340
360
  const cellSelectionSet = useMemo(() => new Set(cellSelection), [cellSelection]);
341
361
  const getCellIsSelected = useCallback((object, { index }) => (
@@ -372,6 +392,13 @@ export function GatingSubscriber(props) {
372
392
  gatingFeatureSelectionX, gatingFeatureSelectionY, obsType,
373
393
  ]);
374
394
 
395
+ const setViewState = ({ zoom: newZoom, target }) => {
396
+ setZoom(newZoom);
397
+ setTargetX(target[0]);
398
+ setTargetY(target[1]);
399
+ setTargetZ(target[2] || 0);
400
+ };
401
+
375
402
  return (
376
403
  <TitleInfo
377
404
  title={title}
@@ -438,13 +465,10 @@ export function GatingSubscriber(props) {
438
465
  uuid={uuid}
439
466
  theme={theme}
440
467
  hideTools={!(gatingFeatureSelectionX && gatingFeatureSelectionY)}
468
+ hideRecenter={!(gatingFeatureSelectionX && gatingFeatureSelectionY)}
441
469
  viewState={{ zoom, target: [targetX, targetY, targetZ] }}
442
- setViewState={({ zoom: newZoom, target }) => {
443
- setZoom(newZoom);
444
- setTargetX(target[0]);
445
- setTargetY(target[1]);
446
- setTargetZ(target[2] || 0);
447
- }}
470
+ setViewState={setViewState}
471
+ originalViewState={originalViewState}
448
472
  obsEmbeddingIndex={obsIndex}
449
473
  obsEmbedding={obsXY}
450
474
  cellFilter={cellFilter}
@@ -470,6 +494,8 @@ export function GatingSubscriber(props) {
470
494
  getExpressionValue={getExpressionValue}
471
495
  getCellIsSelected={getCellIsSelected}
472
496
 
497
+ embeddingPointsVisible
498
+
473
499
  />
474
500
  {!disableTooltip && (
475
501
  <ScatterplotTooltipSubscriber