@vitessce/vit-s 3.4.14 → 3.5.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.
package/dist/index.js CHANGED
@@ -27921,7 +27921,8 @@ const ViewType$1 = {
27921
27921
  OBS_SET_FEATURE_VALUE_DISTRIBUTION: "obsSetFeatureValueDistribution",
27922
27922
  FEATURE_VALUE_HISTOGRAM: "featureValueHistogram",
27923
27923
  DOT_PLOT: "dotPlot",
27924
- FEATURE_BAR_PLOT: "featureBarPlot"
27924
+ FEATURE_BAR_PLOT: "featureBarPlot",
27925
+ BIOMARKER_SELECT: "biomarkerSelect"
27925
27926
  };
27926
27927
  const DataType$1 = {
27927
27928
  OBS_LABELS: "obsLabels",
@@ -28251,10 +28252,10 @@ const AUTO_INDEPENDENT_COORDINATION_TYPES = [
28251
28252
  CoordinationType$1.EMBEDDING_OBS_OPACITY
28252
28253
  ];
28253
28254
  const note = "This file is autogenerated by .changeset/post-changelog.mjs.";
28254
- const version = "3.4.14";
28255
- const date = "2024-10-07";
28255
+ const version = "3.5.1";
28256
+ const date = "2024-11-22";
28256
28257
  const branch = "changeset-release/main";
28257
- const hash = "f416e01c";
28258
+ const hash = "233b667a";
28258
28259
  const META_VERSION = {
28259
28260
  note,
28260
28261
  version,
@@ -29282,7 +29283,7 @@ class VitessceConfigView {
29282
29283
  * @param {number} w The width of the view in the layout.
29283
29284
  * @param {number} h The height of the view in the layout.
29284
29285
  */
29285
- constructor(component, coordinationScopes, x, y, w, h) {
29286
+ constructor(component, coordinationScopes, x, y, w, h, uid) {
29286
29287
  this.view = {
29287
29288
  component,
29288
29289
  coordinationScopes,
@@ -29291,7 +29292,8 @@ class VitessceConfigView {
29291
29292
  x,
29292
29293
  y,
29293
29294
  w,
29294
- h
29295
+ h,
29296
+ uid
29295
29297
  };
29296
29298
  }
29297
29299
  /**
@@ -29545,7 +29547,7 @@ class VitessceConfig {
29545
29547
  * @returns {VitessceConfigView} A new view instance.
29546
29548
  */
29547
29549
  addView(dataset, component, options) {
29548
- const { x = 0, y = 0, w = 1, h = 1, mapping = null } = options || {};
29550
+ const { x = 0, y = 0, w = 1, h = 1, uid = void 0, mapping = null } = options || {};
29549
29551
  const datasetMatches = this.config.coordinationSpace[CoordinationType$1.DATASET] ? Object.entries(this.config.coordinationSpace[CoordinationType$1.DATASET]).filter(([scopeName, datasetScope2]) => datasetScope2.cValue === dataset.dataset.uid).map(([scopeName]) => scopeName) : [];
29550
29552
  let datasetScope;
29551
29553
  if (datasetMatches.length === 1) {
@@ -29556,7 +29558,7 @@ class VitessceConfig {
29556
29558
  const coordinationScopes = {
29557
29559
  [CoordinationType$1.DATASET]: datasetScope
29558
29560
  };
29559
- const newView = new VitessceConfigView(component, coordinationScopes, x, y, w, h);
29561
+ const newView = new VitessceConfigView(component, coordinationScopes, x, y, w, h, uid);
29560
29562
  if (mapping) {
29561
29563
  const [etScope] = this.addCoordination(CoordinationType$1.EMBEDDING_TYPE);
29562
29564
  etScope.setValue(mapping);
@@ -29574,9 +29576,20 @@ class VitessceConfig {
29574
29576
  addCoordination(...args) {
29575
29577
  const cTypes = args;
29576
29578
  const result = [];
29577
- cTypes.forEach((cType) => {
29578
- const prevScopes = this.config.coordinationSpace[cType] ? Object.keys(this.config.coordinationSpace[cType]) : [];
29579
- const scope = new VitessceConfigCoordinationScope(cType, this.getNextScope(prevScopes));
29579
+ cTypes.forEach((cTypeOrObj) => {
29580
+ let cType;
29581
+ let cScope;
29582
+ let cValue;
29583
+ if (typeof cTypeOrObj === "string") {
29584
+ cType = cTypeOrObj;
29585
+ const prevScopes = this.config.coordinationSpace[cType] ? Object.keys(this.config.coordinationSpace[cType]) : [];
29586
+ cScope = this.getNextScope(prevScopes);
29587
+ } else {
29588
+ cType = cTypeOrObj.cType;
29589
+ cScope = cTypeOrObj.cScope;
29590
+ cValue = cTypeOrObj.cValue;
29591
+ }
29592
+ const scope = new VitessceConfigCoordinationScope(cType, cScope, cValue);
29580
29593
  if (!this.config.coordinationSpace[scope.cType]) {
29581
29594
  this.config.coordinationSpace[scope.cType] = {};
29582
29595
  }
@@ -29823,7 +29836,7 @@ class VitessceConfig {
29823
29836
  }
29824
29837
  });
29825
29838
  config2.layout.forEach((c) => {
29826
- const newView = new VitessceConfigView(c.component, c.coordinationScopes, c.x, c.y, c.w, c.h);
29839
+ const newView = new VitessceConfigView(c.component, c.coordinationScopes, c.x, c.y, c.w, c.h, c.uid);
29827
29840
  vc.config.layout.push(newView);
29828
29841
  });
29829
29842
  return vc;
@@ -30776,6 +30789,9 @@ function useSetLoaders() {
30776
30789
  function useMergeCoordination() {
30777
30790
  return useViewConfigStore((state) => state.mergeCoordination);
30778
30791
  }
30792
+ function useViewConfig() {
30793
+ return useViewConfigStore((state) => state.viewConfig);
30794
+ }
30779
30795
  function useSetViewConfig(viewConfigStoreApi) {
30780
30796
  const setViewConfigRef = useRef(viewConfigStoreApi.getState().setViewConfig);
30781
30797
  const setViewConfig = setViewConfigRef.current;
@@ -36713,6 +36729,36 @@ function resolveLayout(layout2) {
36713
36729
  components
36714
36730
  };
36715
36731
  }
36732
+ const AsyncFunctionsContext = createContext$1({
36733
+ // {
36734
+ // asyncFunctions: { [functionType: string]: asyncFunction },
36735
+ // queryClient: QueryClient from react-query
36736
+ // }
36737
+ // asyncFunctions will be an object where keys are function types
36738
+ // (as strings) and values are the async functions.
36739
+ });
36740
+ function useAsyncFunction(functionType2) {
36741
+ var _a;
36742
+ const context = useContext(AsyncFunctionsContext);
36743
+ const origFunction = (_a = context == null ? void 0 : context.asyncFunctions) == null ? void 0 : _a[functionType2];
36744
+ const queryClient = context == null ? void 0 : context.queryClient;
36745
+ const partialFunction = (...args) => origFunction({ queryClient }, ...args);
36746
+ return partialFunction;
36747
+ }
36748
+ const PageModeViewContext = createContext$1({
36749
+ // Keys are view UIDs and values are
36750
+ // the corresponding React components.
36751
+ });
36752
+ function ensureComponent(component) {
36753
+ if (!component) {
36754
+ return () => null;
36755
+ }
36756
+ return component;
36757
+ }
36758
+ function usePageModeView(uid) {
36759
+ const context = useContext(PageModeViewContext);
36760
+ return ensureComponent(context == null ? void 0 : context[uid]);
36761
+ }
36716
36762
  const ResponsiveGridLayout = reactGridLayoutWithLodashExports.WidthProvider(reactGridLayoutWithLodashExports.Responsive);
36717
36763
  class ResponsiveHeightGridLayout extends ResponsiveGridLayout {
36718
36764
  componentDidUpdate(prevProps) {
@@ -36812,30 +36858,23 @@ function VitessceGridLayout(props) {
36812
36858
  const saveCurrentLayouts = useCallback(() => {
36813
36859
  setLastValidGridLayouts(gridLayouts);
36814
36860
  }, [gridLayouts]);
36815
- const layoutChildren = useMemo(() => {
36861
+ const [layoutChildren, pageModeViews] = useMemo(() => {
36816
36862
  const gridComponentsValues = Object.values(gridComponents);
36817
- if (pageMode) {
36818
- return children2.map((child) => {
36819
- const childId = child.props.id;
36820
- const v = gridComponentsValues.find((el2) => el2.uid === childId);
36821
- if (!v) {
36822
- return child;
36823
- }
36824
- const Component = getComponent(v.component);
36825
- return React__default.cloneElement(child, { key: v.uid }, /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { display: "flex", flexDirection: "column", height: "100%" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
36826
- Component,
36827
- {
36828
- ...v.props,
36829
- uuid: v.uid,
36830
- coordinationScopes: v.coordinationScopes,
36831
- coordinationScopesBy: v.coordinationScopesBy,
36832
- theme,
36833
- removeGridComponent: null
36834
- }
36835
- ) }));
36836
- });
36837
- }
36838
- return gridComponentsValues.map((v) => {
36863
+ const contextValue = Object.fromEntries(gridComponentsValues.map((v) => {
36864
+ const Component = getComponent(v.component);
36865
+ return [v.uid, () => /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { display: "flex", flexDirection: "column", height: "100%" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
36866
+ Component,
36867
+ {
36868
+ ...v.props,
36869
+ uuid: v.uid,
36870
+ coordinationScopes: v.coordinationScopes,
36871
+ coordinationScopesBy: v.coordinationScopesBy,
36872
+ theme,
36873
+ removeGridComponent: null
36874
+ }
36875
+ ) }, v.uid)];
36876
+ }));
36877
+ return [gridComponentsValues.map((v) => {
36839
36878
  const Component = getComponent(v.component);
36840
36879
  const removeGridComponent = () => {
36841
36880
  onRemoveComponent(v.uid);
@@ -36851,9 +36890,9 @@ function VitessceGridLayout(props) {
36851
36890
  removeGridComponent
36852
36891
  }
36853
36892
  ) }, v.uid);
36854
- });
36893
+ }), contextValue];
36855
36894
  }, [gridComponents, getComponent, onRemoveComponent, theme]);
36856
- return pageMode ? layoutChildren : gridLayouts && gridComponents && gridBreakpoints && gridCols && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
36895
+ return pageMode ? /* @__PURE__ */ jsxRuntimeExports.jsx(PageModeViewContext.Provider, { value: pageModeViews, children: children2 }) : gridLayouts && gridComponents && gridBreakpoints && gridCols && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
36857
36896
  style,
36858
36897
  /* @__PURE__ */ jsxRuntimeExports.jsx(
36859
36898
  ResponsiveHeightGridLayout,
@@ -37236,18 +37275,22 @@ function useUint8ObsFeatureMatrix({ obsFeatureMatrix }) {
37236
37275
  }
37237
37276
  function useUint8FeatureSelection(expressionData) {
37238
37277
  return useMemo(() => {
37239
- if (expressionData && expressionData[0]) {
37240
- const extents = expressionData.map((arr) => extent(arr));
37241
- const normData = expressionData.map((arr, i) => {
37242
- const [min, max] = extents[i];
37243
- const ratio = 255 / (max - min);
37244
- return new Uint8Array(
37245
- arr.map((j) => Math.floor((j - min) * ratio))
37246
- );
37247
- });
37248
- return [normData, extents];
37278
+ if ((expressionData == null ? void 0 : expressionData[0]) == null) {
37279
+ return { normData: null, extents: null, missing: null };
37249
37280
  }
37250
- return [null, null];
37281
+ const extents = expressionData.map((arr) => extent(arr));
37282
+ const normData = expressionData.map((arr, i) => {
37283
+ const [min, max] = extents[i];
37284
+ const ratio = 255 / (max - min);
37285
+ return new Uint8Array(
37286
+ arr.map((j) => Math.floor((j - min) * ratio))
37287
+ );
37288
+ });
37289
+ const missing = expressionData.map((arr) => {
37290
+ const numMissing = arr.reduce((prev, curr) => Number.isNaN(curr) ? prev + 1 : prev, 0);
37291
+ return numMissing / arr.length;
37292
+ });
37293
+ return { normData, extents, missing };
37251
37294
  }, [expressionData]);
37252
37295
  }
37253
37296
  function useExpressionValueGetter({ instanceObsIndex, matrixObsIndex, expressionData }) {
@@ -38007,14 +38050,6 @@ function createGenerateClassName(customPrefix) {
38007
38050
  return `${seedPrefix}${ruleKey}`;
38008
38051
  };
38009
38052
  }
38010
- const AsyncFunctionsContext = createContext$1({
38011
- // This will be an object where keys are function types
38012
- // (as strings) and values are the async functions.
38013
- });
38014
- function useAsyncFunction(functionType2) {
38015
- const context = useContext(AsyncFunctionsContext);
38016
- return context == null ? void 0 : context[functionType2];
38017
- }
38018
38053
  function VitS(props) {
38019
38054
  const {
38020
38055
  config: config2,
@@ -38049,14 +38084,6 @@ function VitS(props) {
38049
38084
  () => coordinationTypesProp || [],
38050
38085
  [coordinationTypesProp]
38051
38086
  );
38052
- const asyncFunctions = useMemo(
38053
- () => Object.fromEntries(
38054
- // Convert the array of PluginAsyncFunction instances to a mapping
38055
- // from function type strings to async functions.
38056
- (asyncFunctionsProp == null ? void 0 : asyncFunctionsProp.map((p) => [p.functionType, p.asyncFunction])) || []
38057
- ),
38058
- [asyncFunctionsProp]
38059
- );
38060
38087
  const generateClassName2 = useMemo(() => createGenerateClassName(uid), [uid]);
38061
38088
  const configVersion = config2 == null ? void 0 : config2.version;
38062
38089
  const configKey = useMemo(() => {
@@ -38126,7 +38153,20 @@ function VitS(props) {
38126
38153
  retry: 2
38127
38154
  }
38128
38155
  }
38156
+ // TODO: should the queryClient be shared? Or have an option to be shared
38157
+ // (e.g., based on remountOnUidChange)?
38129
38158
  }), [configKey]);
38159
+ const asyncFunctions = useMemo(
38160
+ () => ({
38161
+ queryClient,
38162
+ asyncFunctions: Object.fromEntries(
38163
+ // Convert the array of PluginAsyncFunction instances to a mapping
38164
+ // from function type strings to async functions.
38165
+ (asyncFunctionsProp == null ? void 0 : asyncFunctionsProp.map((p) => [p.functionType, p.asyncFunction])) || []
38166
+ )
38167
+ }),
38168
+ [asyncFunctionsProp, queryClient]
38169
+ );
38130
38170
  useEffect(() => {
38131
38171
  if (success && !isEqual$1(configOrWarning, config2) && onConfigChange) {
38132
38172
  onConfigChange(configOrWarning);
@@ -40235,6 +40275,7 @@ export {
40235
40275
  useObsSegmentationsData,
40236
40276
  useObsSetsData,
40237
40277
  useObsSpotsData,
40278
+ usePageModeView,
40238
40279
  useStyles as usePlotOptionsStyles,
40239
40280
  usePointMultiObsLabels,
40240
40281
  useReady,
@@ -40247,6 +40288,7 @@ export {
40247
40288
  useSegmentationMultiObsSets,
40248
40289
  useSetComponentHover,
40249
40290
  useSetComponentViewInfo,
40291
+ useSetViewConfig,
40250
40292
  useSetWarning,
40251
40293
  useSpotMultiFeatureLabels,
40252
40294
  useSpotMultiFeatureSelection,
@@ -40255,6 +40297,7 @@ export {
40255
40297
  useUint8FeatureSelection,
40256
40298
  useUint8ObsFeatureMatrix,
40257
40299
  useUrls,
40300
+ useViewConfig,
40258
40301
  useViewConfigStore,
40259
40302
  useViewConfigStoreApi,
40260
40303
  useVitessceContainer,
@@ -1 +1 @@
1
- {"version":3,"file":"VitS.d.ts","sourceRoot":"","sources":["../src/VitS.js"],"names":[],"mappings":"AA+BA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,4BAhCG;IAAsB,MAAM,EAApB,MAAM;IAGkB,SAAS,EAAjC,SAAS,GAAC,MAAM;IACF,MAAM,EAApB,MAAM;IACQ,KAAK,EAAnB,MAAM;IAEU,MAAM;IACN,cAAc;IAEd,cAAc;IAEf,cAAc,EAA7B,OAAO;IAIQ,sBAAsB,EAArC,OAAO;IAGY,GAAG,EAAtB,IAAI,GAAC,MAAM;IAGI,kBAAkB,EAAjC,OAAO;IAEM,SAAS,EAAtB,KAAK;IACQ,SAAS,EAAtB,KAAK;IACQ,cAAc,EAA3B,KAAK;IACQ,iBAAiB,EAA9B,KAAK;IACc,OAAO,EAA1B,IAAI,GAAC,MAAM;IACI,QAAQ,EAAvB,OAAO;CAEjB,eA+MA"}
1
+ {"version":3,"file":"VitS.d.ts","sourceRoot":"","sources":["../src/VitS.js"],"names":[],"mappings":"AA+BA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,4BAhCG;IAAsB,MAAM,EAApB,MAAM;IAGkB,SAAS,EAAjC,SAAS,GAAC,MAAM;IACF,MAAM,EAApB,MAAM;IACQ,KAAK,EAAnB,MAAM;IAEU,MAAM;IACN,cAAc;IAEd,cAAc;IAEf,cAAc,EAA7B,OAAO;IAIQ,sBAAsB,EAArC,OAAO;IAGY,GAAG,EAAtB,IAAI,GAAC,MAAM;IAGI,kBAAkB,EAAjC,OAAO;IAEM,SAAS,EAAtB,KAAK;IACQ,SAAS,EAAtB,KAAK;IACQ,cAAc,EAA3B,KAAK;IACQ,iBAAiB,EAA9B,KAAK;IACc,OAAO,EAA1B,IAAI,GAAC,MAAM;IACI,QAAQ,EAAvB,OAAO;CAEjB,eAqNA"}
package/dist-tsc/VitS.js CHANGED
@@ -54,11 +54,6 @@ export function VitS(props) {
54
54
  const fileTypes = useMemo(() => (fileTypesProp || []), [fileTypesProp]);
55
55
  const jointFileTypes = useMemo(() => (jointFileTypesProp || []), [jointFileTypesProp]);
56
56
  const coordinationTypes = useMemo(() => (coordinationTypesProp || []), [coordinationTypesProp]);
57
- const asyncFunctions = useMemo(() => Object.fromEntries(
58
- // Convert the array of PluginAsyncFunction instances to a mapping
59
- // from function type strings to async functions.
60
- asyncFunctionsProp?.map(p => ([p.functionType, p.asyncFunction]))
61
- || []), [asyncFunctionsProp]);
62
57
  const generateClassName = useMemo(() => createGenerateClassName(uid), [uid]);
63
58
  const configVersion = config?.version;
64
59
  // If config.uid exists, then use it for hook dependencies to detect changes
@@ -132,7 +127,17 @@ export function VitS(props) {
132
127
  retry: 2,
133
128
  },
134
129
  },
130
+ // TODO: should the queryClient be shared? Or have an option to be shared
131
+ // (e.g., based on remountOnUidChange)?
135
132
  }), [configKey]);
133
+ const asyncFunctions = useMemo(() => ({
134
+ queryClient,
135
+ asyncFunctions: Object.fromEntries(
136
+ // Convert the array of PluginAsyncFunction instances to a mapping
137
+ // from function type strings to async functions.
138
+ asyncFunctionsProp?.map(p => ([p.functionType, p.asyncFunction]))
139
+ || []),
140
+ }), [asyncFunctionsProp, queryClient]);
136
141
  // Emit the upgraded/initialized view config
137
142
  // to onConfigChange if necessary.
138
143
  useEffect(() => {
@@ -1,3 +1,5 @@
1
- export function useAsyncFunction(functionType: any): any;
1
+ export function useAsyncFunction(functionType: any): (...args: any[]) => any;
2
+ export function usePageModeView(uid: any): any;
2
3
  export const AsyncFunctionsContext: any;
4
+ export const PageModeViewContext: any;
3
5
  //# sourceMappingURL=contexts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"contexts.d.ts","sourceRoot":"","sources":["../src/contexts.js"],"names":[],"mappings":"AAQA,yDAGC;AARD,wCAGG"}
1
+ {"version":3,"file":"contexts.d.ts","sourceRoot":"","sources":["../src/contexts.js"],"names":[],"mappings":"AAYA,6EAQC;AAcD,+CAKC;AApCD,wCAOG;AAYH,sCAGG"}
@@ -1,10 +1,35 @@
1
1
  /* eslint-disable react-refresh/only-export-components */
2
2
  import { createContext, useContext } from 'react';
3
3
  export const AsyncFunctionsContext = createContext({
4
- // This will be an object where keys are function types
4
+ // {
5
+ // asyncFunctions: { [functionType: string]: asyncFunction },
6
+ // queryClient: QueryClient from react-query
7
+ // }
8
+ // asyncFunctions will be an object where keys are function types
5
9
  // (as strings) and values are the async functions.
6
10
  });
7
11
  export function useAsyncFunction(functionType) {
8
12
  const context = useContext(AsyncFunctionsContext);
9
- return context?.[functionType];
13
+ const origFunction = context?.asyncFunctions?.[functionType];
14
+ const queryClient = context?.queryClient;
15
+ // We use partial function application here, so that the caller
16
+ // does not need to worry about passing in the queryClient.
17
+ const partialFunction = (...args) => origFunction({ queryClient }, ...args);
18
+ return partialFunction;
19
+ }
20
+ export const PageModeViewContext = createContext({
21
+ // Keys are view UIDs and values are
22
+ // the corresponding React components.
23
+ });
24
+ function ensureComponent(component) {
25
+ if (!component) {
26
+ return () => null;
27
+ }
28
+ return component;
29
+ }
30
+ export function usePageModeView(uid) {
31
+ const context = useContext(PageModeViewContext);
32
+ // TODO: Is there any case where we would want to
33
+ // throw an error instead?
34
+ return ensureComponent(context?.[uid]);
10
35
  }
@@ -57,15 +57,24 @@ export function useUint8ObsFeatureMatrix({ obsFeatureMatrix }: {
57
57
  /**
58
58
  * Normalize a feature selection (data for selected
59
59
  * columns of an obsFeatureMatrix) to a Uint8Array.
60
- * @param {array|null} expressionData The expressionData
60
+ * @param {Float32Array[] | null} expressionData The expressionData
61
61
  * returned by the useFeatureSelection hook,
62
62
  * where each element corresponds to an
63
63
  * array of values for a selected feature.
64
- * @returns {array} A tuple [normData, extents] where
65
- * normData is an array of Uint8Arrays (or null), and extents is
66
- * an array of [min, max] values for each feature (or null).
64
+ * @returns {{
65
+ * normData: Uint8Array[] | null,
66
+ * extents: [number, number][] | null,
67
+ * missing: number[]
68
+ * }} An object tuple {normData, extents, missing} where
69
+ * normData is an array of Uint8Arrays (or null),
70
+ * extents is an array of [min, max] values for each feature (or null), and
71
+ * missing is an array of numbers (between 0 and 1) for each feature (or null).
67
72
  */
68
- export function useUint8FeatureSelection(expressionData: array | null): array;
73
+ export function useUint8FeatureSelection(expressionData: Float32Array[] | null): {
74
+ normData: Uint8Array[] | null;
75
+ extents: [number, number][] | null;
76
+ missing: number[];
77
+ };
69
78
  export function useExpressionValueGetter({ instanceObsIndex, matrixObsIndex, expressionData }: {
70
79
  instanceObsIndex: any;
71
80
  matrixObsIndex: any;
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.js"],"names":[],"mappings":"AAkBA,oDAOC;AAED;;;;GAIG;AACH,uCAHa,KAAK,CAmBjB;AAED;;;;GAIG;AACH,mCAHa,KAAK,CAoCjB;AAED;;;;;GAKG;AACH,qCAJa,KAAK,CAqCjB;AAED;;;;;;GAMG;AACH,uCAHW,MAAM,EAAE,GACN,OAAO,CAOnB;AAED;;;;GAIG;AACH,8BAHW,CAAC,IAAI,GAAC,MAAM,EAAE,CAAC,EAAE,GACf,KAAK,CAYjB;AAED;;;;;;GAMG;AACH,qDAJW,GAAG,GACD,KAAK,CA0BjB;AAED;;;;;;;;;;GAUG;AACH,+DARG;IAAuB,gBAAgB,EAA/B,MAAM;CAEd,GAAU,KAAK,CAmBjB;AAED;;;;;;;;;;GAUG;AACH,yDARW,KAAK,GAAC,IAAI,GAIR,KAAK,CAmBjB;AAED;;;;QA2BC;AAED,iEAOC;AAED,kHA4BC"}
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.js"],"names":[],"mappings":"AAkBA,oDAOC;AAED;;;;GAIG;AACH,uCAHa,KAAK,CAmBjB;AAED;;;;GAIG;AACH,mCAHa,KAAK,CAoCjB;AAED;;;;;GAKG;AACH,qCAJa,KAAK,CAqCjB;AAED;;;;;;GAMG;AACH,uCAHW,MAAM,EAAE,GACN,OAAO,CAOnB;AAED;;;;GAIG;AACH,8BAHW,CAAC,IAAI,GAAC,MAAM,EAAE,CAAC,EAAE,GACf,KAAK,CAYjB;AAED;;;;;;GAMG;AACH,qDAJW,GAAG,GACD,KAAK,CA0BjB;AAED;;;;;;;;;;GAUG;AACH,+DARG;IAAuB,gBAAgB,EAA/B,MAAM;CAEd,GAAU,KAAK,CAmBjB;AAED;;;;;;;;;;;;;;;GAeG;AACH,yDAbW,YAAY,EAAE,GAAG,IAAI,GAInB;IACZ,QAAW,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;IACjC,OAAU,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,IAAI,CAAC;IACtC,OAAU,EAAE,MAAM,EAAE,CAAA;CACjB,CAwBH;AAED;;;;QA2BC;AAED,iEAOC;AAED,kHA4BC"}
package/dist-tsc/hooks.js CHANGED
@@ -185,26 +185,35 @@ export function useUint8ObsFeatureMatrix({ obsFeatureMatrix }) {
185
185
  /**
186
186
  * Normalize a feature selection (data for selected
187
187
  * columns of an obsFeatureMatrix) to a Uint8Array.
188
- * @param {array|null} expressionData The expressionData
188
+ * @param {Float32Array[] | null} expressionData The expressionData
189
189
  * returned by the useFeatureSelection hook,
190
190
  * where each element corresponds to an
191
191
  * array of values for a selected feature.
192
- * @returns {array} A tuple [normData, extents] where
193
- * normData is an array of Uint8Arrays (or null), and extents is
194
- * an array of [min, max] values for each feature (or null).
192
+ * @returns {{
193
+ * normData: Uint8Array[] | null,
194
+ * extents: [number, number][] | null,
195
+ * missing: number[]
196
+ * }} An object tuple {normData, extents, missing} where
197
+ * normData is an array of Uint8Arrays (or null),
198
+ * extents is an array of [min, max] values for each feature (or null), and
199
+ * missing is an array of numbers (between 0 and 1) for each feature (or null).
195
200
  */
196
201
  export function useUint8FeatureSelection(expressionData) {
197
202
  return useMemo(() => {
198
- if (expressionData && expressionData[0]) {
199
- const extents = expressionData.map(arr => extent(arr));
200
- const normData = expressionData.map((arr, i) => {
201
- const [min, max] = extents[i];
202
- const ratio = 255 / (max - min);
203
- return new Uint8Array(arr.map(j => Math.floor((j - min) * ratio)));
204
- });
205
- return [normData, extents];
203
+ if (expressionData?.[0] == null) {
204
+ return { normData: null, extents: null, missing: null };
206
205
  }
207
- return [null, null];
206
+ const extents = expressionData.map(arr => extent(arr));
207
+ const normData = expressionData.map((arr, i) => {
208
+ const [min, max] = extents[i];
209
+ const ratio = 255 / (max - min);
210
+ return new Uint8Array(arr.map(j => Math.floor((j - min) * ratio)));
211
+ });
212
+ const missing = expressionData.map((arr) => {
213
+ const numMissing = arr.reduce((prev, curr) => (Number.isNaN(curr) ? prev + 1 : prev), 0);
214
+ return numMissing / arr.length;
215
+ });
216
+ return { normData, extents, missing };
208
217
  }, [expressionData]);
209
218
  }
210
219
  export function useExpressionValueGetter({ instanceObsIndex, matrixObsIndex, expressionData }) {
@@ -3,12 +3,12 @@ export { TitleInfo } from "./TitleInfo.js";
3
3
  export { PopperMenu } from "./shared-mui/components.js";
4
4
  export { useHasLoader } from "./data-hook-utils.js";
5
5
  export { logConfig } from "./view-config-utils.js";
6
- export { useAsyncFunction } from "./contexts.js";
7
6
  export { useReady, useUrls, useVitessceContainer, useDeckCanvasSize, useUint8ObsFeatureMatrix, useUint8FeatureSelection, useExpressionValueGetter, useGetObsMembership, useGetObsInfo, useClosestVitessceContainerSize, useWindowDimensions, useGridItemSize } from "./hooks.js";
8
- export { useCoordinationScopes, useCoordinationScopesBy, useInitialCoordination, useCoordination, useComplexCoordination, useComplexCoordinationSecondary, useMultiCoordinationScopes, useMultiCoordinationScopesNonNull, useMultiCoordinationScopesSecondary, useMultiCoordinationScopesSecondaryNonNull, useMultiCoordinationValues, useMultiDatasetCoordination, useDatasetUids, useLoaders, useMergeCoordination, useMatchingLoader, useViewConfigStore, useViewConfigStoreApi, useComponentHover, useSetComponentHover, useComponentViewInfo, useSetComponentViewInfo, useWarning, useSetWarning, useAuxiliaryCoordination, useComponentLayout, useRemoveImageChannelInMetaCoordinationScopes, useAddImageChannelInMetaCoordinationScopes } from "./state/hooks.js";
7
+ export { useCoordinationScopes, useCoordinationScopesBy, useInitialCoordination, useCoordination, useComplexCoordination, useComplexCoordinationSecondary, useMultiCoordinationScopes, useMultiCoordinationScopesNonNull, useMultiCoordinationScopesSecondary, useMultiCoordinationScopesSecondaryNonNull, useMultiCoordinationValues, useMultiDatasetCoordination, useDatasetUids, useLoaders, useMergeCoordination, useMatchingLoader, useViewConfigStore, useViewConfigStoreApi, useViewConfig, useSetViewConfig, useComponentHover, useSetComponentHover, useComponentViewInfo, useSetComponentViewInfo, useWarning, useSetWarning, useAuxiliaryCoordination, useComponentLayout, useRemoveImageChannelInMetaCoordinationScopes, useAddImageChannelInMetaCoordinationScopes } from "./state/hooks.js";
9
8
  export { useDescription, useImageData, useObsSetsData, useObsEmbeddingData, useFeatureSelection, useObsFeatureMatrixIndices, useSampleSetsData, useSampleEdgesData, useMultiObsLabels, useMultiObsSpots, useMultiObsPoints, useSpotMultiObsSets, useSpotMultiFeatureLabels, useMultiObsSegmentations, useMultiImages, useObsSpotsData, useObsPointsData, useObsLocationsData, useObsSegmentationsData, useNeighborhoodsData, useObsLabelsData, useObsFeatureMatrixData, useFeatureLabelsData, useGenomicProfilesData } from "./data-hooks.js";
10
9
  export { usePointMultiObsLabels, useSpotMultiFeatureSelection, useSpotMultiObsFeatureMatrixIndices, useSegmentationMultiFeatureSelection, useSegmentationMultiObsFeatureMatrixIndices, useSegmentationMultiObsLocations, useSegmentationMultiObsSets } from "./data-hooks-multilevel.js";
11
10
  export { AbstractLoader, AbstractTwoStepLoader, LoaderResult } from "./data/index.js";
12
11
  export { AbstractLoaderError, DatasetNotFoundError, LoaderNotFoundError, LoaderValidationError, DataSourceFetchError } from "./errors/index.js";
13
12
  export { CellColorEncodingOption, OptionsContainer, OptionSelect, usePlotOptionsStyles } from "./shared-plot-options/index.js";
13
+ export { useAsyncFunction, usePageModeView } from "./contexts.js";
14
14
  //# sourceMappingURL=index.d.ts.map
package/dist-tsc/index.js CHANGED
@@ -3,7 +3,7 @@ export { TitleInfo } from './TitleInfo.js';
3
3
  export { PopperMenu } from './shared-mui/components.js';
4
4
  // For plugin view types:
5
5
  export { useReady, useUrls, useVitessceContainer, useDeckCanvasSize, useUint8ObsFeatureMatrix, useUint8FeatureSelection, useExpressionValueGetter, useGetObsMembership, useGetObsInfo, useClosestVitessceContainerSize, useWindowDimensions, useGridItemSize, } from './hooks.js';
6
- export { useCoordinationScopes, useCoordinationScopesBy, useInitialCoordination, useCoordination, useComplexCoordination, useComplexCoordinationSecondary, useMultiCoordinationScopes, useMultiCoordinationScopesNonNull, useMultiCoordinationScopesSecondary, useMultiCoordinationScopesSecondaryNonNull, useMultiCoordinationValues, useMultiDatasetCoordination, useDatasetUids, useLoaders, useMergeCoordination, useMatchingLoader, useViewConfigStore, useViewConfigStoreApi, useComponentHover, useSetComponentHover, useComponentViewInfo, useSetComponentViewInfo, useWarning, useSetWarning, useAuxiliaryCoordination, useComponentLayout, useRemoveImageChannelInMetaCoordinationScopes, useAddImageChannelInMetaCoordinationScopes, } from './state/hooks.js';
6
+ export { useCoordinationScopes, useCoordinationScopesBy, useInitialCoordination, useCoordination, useComplexCoordination, useComplexCoordinationSecondary, useMultiCoordinationScopes, useMultiCoordinationScopesNonNull, useMultiCoordinationScopesSecondary, useMultiCoordinationScopesSecondaryNonNull, useMultiCoordinationValues, useMultiDatasetCoordination, useDatasetUids, useLoaders, useMergeCoordination, useMatchingLoader, useViewConfigStore, useViewConfigStoreApi, useViewConfig, useSetViewConfig, useComponentHover, useSetComponentHover, useComponentViewInfo, useSetComponentViewInfo, useWarning, useSetWarning, useAuxiliaryCoordination, useComponentLayout, useRemoveImageChannelInMetaCoordinationScopes, useAddImageChannelInMetaCoordinationScopes, } from './state/hooks.js';
7
7
  export { useDescription, useImageData, useObsSetsData, useObsEmbeddingData, useFeatureSelection, useObsFeatureMatrixIndices, useSampleSetsData, useSampleEdgesData, useMultiObsLabels, useMultiObsSpots, useMultiObsPoints, useSpotMultiObsSets, useSpotMultiFeatureLabels, useMultiObsSegmentations, useMultiImages, useObsSpotsData, useObsPointsData, useObsLocationsData, useObsSegmentationsData, useNeighborhoodsData, useObsLabelsData, useObsFeatureMatrixData, useFeatureLabelsData, useGenomicProfilesData, } from './data-hooks.js';
8
8
  export { usePointMultiObsLabels, useSpotMultiFeatureSelection, useSpotMultiObsFeatureMatrixIndices, useSegmentationMultiFeatureSelection, useSegmentationMultiObsFeatureMatrixIndices, useSegmentationMultiObsLocations, useSegmentationMultiObsSets, } from './data-hooks-multilevel.js';
9
9
  export { useHasLoader, } from './data-hook-utils.js';
@@ -11,4 +11,4 @@ export { AbstractLoader, AbstractTwoStepLoader, LoaderResult, } from './data/ind
11
11
  export { AbstractLoaderError, DatasetNotFoundError, LoaderNotFoundError, LoaderValidationError, DataSourceFetchError, } from './errors/index.js';
12
12
  export { CellColorEncodingOption, OptionsContainer, OptionSelect, usePlotOptionsStyles, } from './shared-plot-options/index.js';
13
13
  export { logConfig } from './view-config-utils.js';
14
- export { useAsyncFunction } from './contexts.js';
14
+ export { useAsyncFunction, usePageModeView } from './contexts.js';
@@ -222,6 +222,13 @@ export function useSetLoaders(): Function;
222
222
  * in the `useViewConfigStore` store.
223
223
  */
224
224
  export function useMergeCoordination(): Function;
225
+ /**
226
+ * Obtain the current view config object from
227
+ * the global app state.
228
+ * @returns {object} The view config object
229
+ * in the `useViewConfigStore` store.
230
+ */
231
+ export function useViewConfig(): object;
225
232
  /**
226
233
  * Obtain the view config setter function from
227
234
  * the global app state.
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/state/hooks.js"],"names":[],"mappings":"AAmCA;;;;;;GAMG;AACH,8CAJW,GAAC,mBAEC,MAAM,GAAC,SAAS,CA4B5B;AAED;;;;;;;GAOG;AACH,gDALW,GAAC,wBACD,GAAC,qBAEC,MAAM,GAAC,SAAS,CA0C5B;AAED;;;;;GAKG;AACH,6CAJW,MAAM,sBACN,GAAC,GACC,MAAM,GAAC,SAAS,CAI5B;AAED;;;;;;GAMG;AACH,+CALW,MAAM,mDACN,GAAC,wBACD,GAAC,GACC,MAAM,GAAC,SAAS,CAc5B;AA4RD;;;;;;;;;GASG;AACH,mDALW,MAAM,EAAE,sBACR,MAAM,GAEJ,MAAM,CAclB;AAED;;;;;;;;;;;;;;;GAeG;AACH,4CATW,MAAM,EAAE,sBACR,MAAM,GAEJ,KAAK,CAmCjB;AAED,yFAKC;AAED,gGAqBC;AAED,0IA4BC;AAED,iJAkEC;AAED,yFAmBC;AAED;;;;GAIG;AACH,mDAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;;;;;;;;;;GAaG;AACH,mDAXW,MAAM,EAAE,sBACR,MAAM,wBACN,MAAM,UAEN,MAAM,GAEJ,KAAK,CA2EjB;AAED;;;;;GAKG;AACH,0DAHW,MAAM,GACJ,MAAM,CAiBlB;AAED;;;;;;GAMG;AACH,4DAJW,MAAM,wBACN,MAAM,GACJ,MAAM,CAgBlB;AAED;;;;;;;GAOG;AACH,4DANW,MAAM,EAAE,iDACR,MAAM,eACN,MAAM,iBACN,MAAM,SAuEhB;AAGD;;;;;;;;;;;GAWG;AACH,wDATW,MAAM,EAAE,sBACR,MAAM,wBACN,MAAM,GAEJ,KAAK,CAUjB;AA6BD;;;;;;;;;;;;;;;GAeG;AACH,qDATW,MAAM,EAAE,sBACR,MAAM,GAEJ,KAAK,CAgCjB;AAED;;;;;GAKG;AACH,8BAHa,MAAM,CAKlB;AAGD;;;;;;;;;;;;GAYG;AACH,2CARW,MAAM,WACN,MAAM,YACN,MAAM,0BACN,MAAM,OAoBhB;AAED;;;;;;;;;;;;GAYG;AACH,2CARW,MAAM,WACN,MAAM,YACN,MAAM,0BACN,MAAM,OAShB;AAED;;;;;GAKG;AACH,6BAHa,MAAM,EAAE,CAKpB;AAED;;;;;GAKG;AACH,+CAEC;AAED;;;;;GAKG;AACH,0EAEC;AAED;;;;;GAKG;AACH,uEAEC;AAED;;;;;GAKG;AACH,4CAEC;AAED;;;;;GAKG;AACH,0CAEC;AAED;;;;;GAKG;AACH,iDAEC;AAED;;;;;GAKG;AACH,oEAIC;AAED;;;;;GAKG;AACH,qCAHa,MAAM,CAKlB;AAED;;;;;GAKG;AACH,iDAEC;AAED;;;;;GAKG;AACH,8BAHa,MAAM,CAKlB;AAED;;;;;GAKG;AACH,0CAEC;AAED;;;;;GAKG;AACH,iDAHa,MAAM,CAKlB;AAED;;;;;GAKG;AACH,6DAOC;AAED;;;;GAIG;AACH,iCAFa,MAAM,CAIlB;AAED;;;;;GAKG;AACH,8CAEC;AA5oCD;;;;aAPG,OAAO,kCAAkC,OAAO,sEAOO;AAC1D,mFAA0D;AAC1D;;;;;EAAgE;AAOhE;;;;mIAAwD;AACxD,kFAAwD;AAiIjD,yFA+KJ;AAOI,0FAFM,MAAM,CAOlB;AAcM,iDAUJ"}
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/state/hooks.js"],"names":[],"mappings":"AAmCA;;;;;;GAMG;AACH,8CAJW,GAAC,mBAEC,MAAM,GAAC,SAAS,CA4B5B;AAED;;;;;;;GAOG;AACH,gDALW,GAAC,wBACD,GAAC,qBAEC,MAAM,GAAC,SAAS,CA0C5B;AAED;;;;;GAKG;AACH,6CAJW,MAAM,sBACN,GAAC,GACC,MAAM,GAAC,SAAS,CAI5B;AAED;;;;;;GAMG;AACH,+CALW,MAAM,mDACN,GAAC,wBACD,GAAC,GACC,MAAM,GAAC,SAAS,CAc5B;AA4RD;;;;;;;;;GASG;AACH,mDALW,MAAM,EAAE,sBACR,MAAM,GAEJ,MAAM,CAclB;AAED;;;;;;;;;;;;;;;GAeG;AACH,4CATW,MAAM,EAAE,sBACR,MAAM,GAEJ,KAAK,CAmCjB;AAED,yFAKC;AAED,gGAqBC;AAED,0IA4BC;AAED,iJAkEC;AAED,yFAmBC;AAED;;;;GAIG;AACH,mDAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;;;;;;;;;;GAaG;AACH,mDAXW,MAAM,EAAE,sBACR,MAAM,wBACN,MAAM,UAEN,MAAM,GAEJ,KAAK,CA2EjB;AAED;;;;;GAKG;AACH,0DAHW,MAAM,GACJ,MAAM,CAiBlB;AAED;;;;;;GAMG;AACH,4DAJW,MAAM,wBACN,MAAM,GACJ,MAAM,CAgBlB;AAED;;;;;;;GAOG;AACH,4DANW,MAAM,EAAE,iDACR,MAAM,eACN,MAAM,iBACN,MAAM,SAuEhB;AAGD;;;;;;;;;;;GAWG;AACH,wDATW,MAAM,EAAE,sBACR,MAAM,wBACN,MAAM,GAEJ,KAAK,CAUjB;AA6BD;;;;;;;;;;;;;;;GAeG;AACH,qDATW,MAAM,EAAE,sBACR,MAAM,GAEJ,KAAK,CAgCjB;AAED;;;;;GAKG;AACH,8BAHa,MAAM,CAKlB;AAGD;;;;;;;;;;;;GAYG;AACH,2CARW,MAAM,WACN,MAAM,YACN,MAAM,0BACN,MAAM,OAoBhB;AAED;;;;;;;;;;;;GAYG;AACH,2CARW,MAAM,WACN,MAAM,YACN,MAAM,0BACN,MAAM,OAShB;AAED;;;;;GAKG;AACH,6BAHa,MAAM,EAAE,CAKpB;AAED;;;;;GAKG;AACH,+CAEC;AAED;;;;;GAKG;AACH,0EAEC;AAED;;;;;GAKG;AACH,uEAEC;AAED;;;;;GAKG;AACH,4CAEC;AAED;;;;;GAKG;AACH,0CAEC;AAED;;;;;GAKG;AACH,iDAEC;AAED;;;;;GAKG;AACH,iCAHa,MAAM,CAKlB;AAED;;;;;GAKG;AACH,oEAIC;AAGD;;;;;GAKG;AACH,qCAHa,MAAM,CAKlB;AAED;;;;;GAKG;AACH,iDAEC;AAED;;;;;GAKG;AACH,8BAHa,MAAM,CAKlB;AAED;;;;;GAKG;AACH,0CAEC;AAED;;;;;GAKG;AACH,iDAHa,MAAM,CAKlB;AAED;;;;;GAKG;AACH,6DAOC;AAED;;;;GAIG;AACH,iCAFa,MAAM,CAIlB;AAED;;;;;GAKG;AACH,8CAEC;AAvpCD;;;;aAPG,OAAO,kCAAkC,OAAO,sEAOO;AAC1D,mFAA0D;AAC1D;;;;;EAAgE;AAOhE;;;;mIAAwD;AACxD,kFAAwD;AAiIjD,yFA+KJ;AAOI,0FAFM,MAAM,CAOlB;AAcM,iDAUJ"}
@@ -973,6 +973,15 @@ export function useSetLoaders() {
973
973
  export function useMergeCoordination() {
974
974
  return useViewConfigStore(state => state.mergeCoordination);
975
975
  }
976
+ /**
977
+ * Obtain the current view config object from
978
+ * the global app state.
979
+ * @returns {object} The view config object
980
+ * in the `useViewConfigStore` store.
981
+ */
982
+ export function useViewConfig() {
983
+ return useViewConfigStore(state => state.viewConfig);
984
+ }
976
985
  /**
977
986
  * Obtain the view config setter function from
978
987
  * the global app state.
@@ -1 +1 @@
1
- {"version":3,"file":"VitessceGridLayout.d.ts","sourceRoot":"","sources":["../../src/vitessce-grid-layout/VitessceGridLayout.js"],"names":[],"mappings":"AAeA,oDA8LC"}
1
+ {"version":3,"file":"VitessceGridLayout.d.ts","sourceRoot":"","sources":["../../src/vitessce-grid-layout/VitessceGridLayout.js"],"names":[],"mappings":"AAgBA,oDAwLC"}
@@ -3,6 +3,7 @@ import React, { useState, useMemo, useCallback } from 'react';
3
3
  import { Responsive, WidthProvider } from 'react-grid-layout-with-lodash';
4
4
  import { isEqual } from 'lodash-es';
5
5
  import { getMaxRows, resolveLayout } from './layout-utils.js';
6
+ import { PageModeViewContext } from '../contexts.js';
6
7
  const ResponsiveGridLayout = WidthProvider(Responsive);
7
8
  class ResponsiveHeightGridLayout extends ResponsiveGridLayout {
8
9
  componentDidUpdate(prevProps) {
@@ -97,38 +98,26 @@ export function VitessceGridLayout(props) {
97
98
  const saveCurrentLayouts = useCallback(() => {
98
99
  setLastValidGridLayouts(gridLayouts);
99
100
  }, [gridLayouts]);
100
- const layoutChildren = useMemo(() => {
101
+ const [layoutChildren, pageModeViews] = useMemo(() => {
101
102
  const gridComponentsValues = Object.values(gridComponents);
102
- if (pageMode) {
103
- // This is pageMode, so we render based on the children.
104
- return children.map((child) => {
105
- const childId = child.props.id;
106
- const v = gridComponentsValues.find(el => el.uid === childId);
107
- if (!v) {
108
- // The child did not map to any view in the config, so return as-is.
109
- return child;
110
- }
111
- // The child mapped to a view in the config,
112
- // so we want to render the corresponding component as a subchild.
113
- const Component = getComponent(v.component);
114
- return React.cloneElement(child, { key: v.uid }, (_jsx("div", { style: { display: 'flex', flexDirection: 'column', height: '100%' }, children: _jsx(Component, { ...v.props, uuid: v.uid, coordinationScopes: v.coordinationScopes, coordinationScopesBy: v.coordinationScopesBy, theme: theme, removeGridComponent: null }) })));
115
- });
116
- }
103
+ const contextValue = Object.fromEntries(gridComponentsValues.map((v) => {
104
+ const Component = getComponent(v.component);
105
+ return [v.uid, () => (_jsx("div", { style: { display: 'flex', flexDirection: 'column', height: '100%' }, children: _jsx(Component, { ...v.props, uuid: v.uid, coordinationScopes: v.coordinationScopes, coordinationScopesBy: v.coordinationScopesBy, theme: theme, removeGridComponent: null }) }, v.uid))];
106
+ }));
117
107
  // Not pageMode, so no children to render.
118
108
  // We render the grid components based on the layout information.
119
- return gridComponentsValues.map((v) => {
120
- const Component = getComponent(v.component);
121
- const removeGridComponent = () => {
122
- onRemoveComponent(v.uid);
123
- };
124
- return (_jsx("div", { children: _jsx(Component, { ...v.props, uuid: v.uid, coordinationScopes: v.coordinationScopes, coordinationScopesBy: v.coordinationScopesBy, theme: theme, removeGridComponent: removeGridComponent }) }, v.uid));
125
- });
109
+ return [gridComponentsValues.map((v) => {
110
+ const Component = getComponent(v.component);
111
+ const removeGridComponent = () => {
112
+ onRemoveComponent(v.uid);
113
+ };
114
+ return (_jsx("div", { children: _jsx(Component, { ...v.props, uuid: v.uid, coordinationScopes: v.coordinationScopes, coordinationScopesBy: v.coordinationScopesBy, theme: theme, removeGridComponent: removeGridComponent }) }, v.uid));
115
+ }), contextValue];
126
116
  }, [gridComponents, getComponent, onRemoveComponent, theme]);
127
117
  return (pageMode
128
- ? layoutChildren
129
- : ((gridLayouts && gridComponents && gridBreakpoints && gridCols) && (_jsxs(_Fragment, { children: [style, _jsx(ResponsiveHeightGridLayout, { className: "layout", cols: gridCols, layouts: tempGridLayouts || gridLayouts, breakpoints: gridBreakpoints, height: height, rowHeight: rowHeight
130
- || ((window.innerHeight - 2 * padding - (maxRows - 1) * margin)
131
- / maxRows), containerPadding: containerPadding, margin: margin, draggableHandle: draggableHandle, onLayoutChange: onLayoutChange, isBounded: isBounded, onResizeStart: saveCurrentLayouts, onDragStart: saveCurrentLayouts, onResize: onResize, onResizeStop: onResizeStop, children: layoutChildren })] }))));
118
+ ? (_jsx(PageModeViewContext.Provider, { value: pageModeViews, children: children })) : ((gridLayouts && gridComponents && gridBreakpoints && gridCols) && (_jsxs(_Fragment, { children: [style, _jsx(ResponsiveHeightGridLayout, { className: "layout", cols: gridCols, layouts: tempGridLayouts || gridLayouts, breakpoints: gridBreakpoints, height: height, rowHeight: rowHeight
119
+ || ((window.innerHeight - 2 * padding - (maxRows - 1) * margin)
120
+ / maxRows), containerPadding: containerPadding, margin: margin, draggableHandle: draggableHandle, onLayoutChange: onLayoutChange, isBounded: isBounded, onResizeStart: saveCurrentLayouts, onDragStart: saveCurrentLayouts, onResize: onResize, onResizeStop: onResizeStop, children: layoutChildren })] }))));
132
121
  }
133
122
  VitessceGridLayout.defaultProps = {
134
123
  padding: 10,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vitessce/vit-s",
3
- "version": "3.4.14",
4
- "author": "Gehlenborg Lab",
3
+ "version": "3.5.1",
4
+ "author": "HIDIVE Lab at HMS",
5
5
  "homepage": "http://vitessce.io",
6
6
  "repository": {
7
7
  "type": "git",
@@ -29,12 +29,12 @@
29
29
  "uuid": "^9.0.0",
30
30
  "zustand": "^3.5.10",
31
31
  "react-aria": "^3.28.0",
32
- "@vitessce/constants-internal": "3.4.14",
33
- "@vitessce/plugins": "3.4.14",
34
- "@vitessce/schemas": "3.4.14",
35
- "@vitessce/utils": "3.4.14",
36
- "@vitessce/sets-utils": "3.4.14",
37
- "@vitessce/config": "3.4.14"
32
+ "@vitessce/constants-internal": "3.5.1",
33
+ "@vitessce/plugins": "3.5.1",
34
+ "@vitessce/schemas": "3.5.1",
35
+ "@vitessce/utils": "3.5.1",
36
+ "@vitessce/sets-utils": "3.5.1",
37
+ "@vitessce/config": "3.5.1"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@testing-library/jest-dom": "^5.16.4",
@@ -42,7 +42,7 @@
42
42
  "react": "^18.0.0",
43
43
  "vite": "^4.3.0",
44
44
  "vitest": "^0.32.2",
45
- "@vitessce/types": "3.4.14"
45
+ "@vitessce/types": "3.5.1"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
package/src/VitS.js CHANGED
@@ -99,15 +99,6 @@ export function VitS(props) {
99
99
  () => (coordinationTypesProp || []),
100
100
  [coordinationTypesProp],
101
101
  );
102
- const asyncFunctions = useMemo(
103
- () => Object.fromEntries(
104
- // Convert the array of PluginAsyncFunction instances to a mapping
105
- // from function type strings to async functions.
106
- asyncFunctionsProp?.map(p => ([p.functionType, p.asyncFunction]))
107
- || [],
108
- ),
109
- [asyncFunctionsProp],
110
- );
111
102
 
112
103
  const generateClassName = useMemo(() => createGenerateClassName(uid), [uid]);
113
104
 
@@ -196,8 +187,23 @@ export function VitS(props) {
196
187
  retry: 2,
197
188
  },
198
189
  },
190
+ // TODO: should the queryClient be shared? Or have an option to be shared
191
+ // (e.g., based on remountOnUidChange)?
199
192
  }), [configKey]);
200
193
 
194
+ const asyncFunctions = useMemo(
195
+ () => ({
196
+ queryClient,
197
+ asyncFunctions: Object.fromEntries(
198
+ // Convert the array of PluginAsyncFunction instances to a mapping
199
+ // from function type strings to async functions.
200
+ asyncFunctionsProp?.map(p => ([p.functionType, p.asyncFunction]))
201
+ || [],
202
+ ),
203
+ }),
204
+ [asyncFunctionsProp, queryClient],
205
+ );
206
+
201
207
  // Emit the upgraded/initialized view config
202
208
  // to onConfigChange if necessary.
203
209
  useEffect(() => {
package/src/contexts.js CHANGED
@@ -2,11 +2,39 @@
2
2
  import { createContext, useContext } from 'react';
3
3
 
4
4
  export const AsyncFunctionsContext = createContext({
5
- // This will be an object where keys are function types
5
+ // {
6
+ // asyncFunctions: { [functionType: string]: asyncFunction },
7
+ // queryClient: QueryClient from react-query
8
+ // }
9
+ // asyncFunctions will be an object where keys are function types
6
10
  // (as strings) and values are the async functions.
7
11
  });
8
12
 
9
13
  export function useAsyncFunction(functionType) {
10
14
  const context = useContext(AsyncFunctionsContext);
11
- return context?.[functionType];
15
+ const origFunction = context?.asyncFunctions?.[functionType];
16
+ const queryClient = context?.queryClient;
17
+ // We use partial function application here, so that the caller
18
+ // does not need to worry about passing in the queryClient.
19
+ const partialFunction = (...args) => origFunction({ queryClient }, ...args);
20
+ return partialFunction;
21
+ }
22
+
23
+ export const PageModeViewContext = createContext({
24
+ // Keys are view UIDs and values are
25
+ // the corresponding React components.
26
+ });
27
+
28
+ function ensureComponent(component) {
29
+ if (!component) {
30
+ return () => null;
31
+ }
32
+ return component;
33
+ }
34
+
35
+ export function usePageModeView(uid) {
36
+ const context = useContext(PageModeViewContext);
37
+ // TODO: Is there any case where we would want to
38
+ // throw an error instead?
39
+ return ensureComponent(context?.[uid]);
12
40
  }
package/src/hooks.js CHANGED
@@ -221,28 +221,37 @@ export function useUint8ObsFeatureMatrix({ obsFeatureMatrix }) {
221
221
  /**
222
222
  * Normalize a feature selection (data for selected
223
223
  * columns of an obsFeatureMatrix) to a Uint8Array.
224
- * @param {array|null} expressionData The expressionData
224
+ * @param {Float32Array[] | null} expressionData The expressionData
225
225
  * returned by the useFeatureSelection hook,
226
226
  * where each element corresponds to an
227
227
  * array of values for a selected feature.
228
- * @returns {array} A tuple [normData, extents] where
229
- * normData is an array of Uint8Arrays (or null), and extents is
230
- * an array of [min, max] values for each feature (or null).
228
+ * @returns {{
229
+ * normData: Uint8Array[] | null,
230
+ * extents: [number, number][] | null,
231
+ * missing: number[]
232
+ * }} An object tuple {normData, extents, missing} where
233
+ * normData is an array of Uint8Arrays (or null),
234
+ * extents is an array of [min, max] values for each feature (or null), and
235
+ * missing is an array of numbers (between 0 and 1) for each feature (or null).
231
236
  */
232
237
  export function useUint8FeatureSelection(expressionData) {
233
238
  return useMemo(() => {
234
- if (expressionData && expressionData[0]) {
235
- const extents = expressionData.map(arr => extent(arr));
236
- const normData = expressionData.map((arr, i) => {
237
- const [min, max] = extents[i];
238
- const ratio = 255 / (max - min);
239
- return new Uint8Array(
240
- arr.map(j => Math.floor((j - min) * ratio)),
241
- );
242
- });
243
- return [normData, extents];
239
+ if (expressionData?.[0] == null) {
240
+ return { normData: null, extents: null, missing: null };
244
241
  }
245
- return [null, null];
242
+ const extents = expressionData.map(arr => extent(arr));
243
+ const normData = expressionData.map((arr, i) => {
244
+ const [min, max] = extents[i];
245
+ const ratio = 255 / (max - min);
246
+ return new Uint8Array(
247
+ arr.map(j => Math.floor((j - min) * ratio)),
248
+ );
249
+ });
250
+ const missing = expressionData.map((arr) => {
251
+ const numMissing = arr.reduce((prev, curr) => (Number.isNaN(curr) ? prev + 1 : prev), 0);
252
+ return numMissing / arr.length;
253
+ });
254
+ return { normData, extents, missing };
246
255
  }, [expressionData]);
247
256
  }
248
257
 
package/src/index.js CHANGED
@@ -35,6 +35,8 @@ export {
35
35
  useMatchingLoader,
36
36
  useViewConfigStore,
37
37
  useViewConfigStoreApi,
38
+ useViewConfig,
39
+ useSetViewConfig,
38
40
  useComponentHover,
39
41
  useSetComponentHover,
40
42
  useComponentViewInfo,
@@ -105,4 +107,4 @@ export {
105
107
  usePlotOptionsStyles,
106
108
  } from './shared-plot-options/index.js';
107
109
  export { logConfig } from './view-config-utils.js';
108
- export { useAsyncFunction } from './contexts.js';
110
+ export { useAsyncFunction, usePageModeView } from './contexts.js';
@@ -1091,6 +1091,16 @@ export function useMergeCoordination() {
1091
1091
  return useViewConfigStore(state => state.mergeCoordination);
1092
1092
  }
1093
1093
 
1094
+ /**
1095
+ * Obtain the current view config object from
1096
+ * the global app state.
1097
+ * @returns {object} The view config object
1098
+ * in the `useViewConfigStore` store.
1099
+ */
1100
+ export function useViewConfig() {
1101
+ return useViewConfigStore(state => state.viewConfig);
1102
+ }
1103
+
1094
1104
  /**
1095
1105
  * Obtain the view config setter function from
1096
1106
  * the global app state.
@@ -1103,6 +1113,7 @@ export function useSetViewConfig(viewConfigStoreApi) {
1103
1113
  return setViewConfig;
1104
1114
  }
1105
1115
 
1116
+
1106
1117
  /**
1107
1118
  * Obtain the component hover value from
1108
1119
  * the global app state.
@@ -2,6 +2,7 @@ import React, { useState, useMemo, useCallback } from 'react';
2
2
  import { Responsive, WidthProvider } from 'react-grid-layout-with-lodash';
3
3
  import { isEqual } from 'lodash-es';
4
4
  import { getMaxRows, resolveLayout } from './layout-utils.js';
5
+ import { PageModeViewContext } from '../contexts.js';
5
6
 
6
7
  const ResponsiveGridLayout = WidthProvider(Responsive);
7
8
 
@@ -122,37 +123,28 @@ export function VitessceGridLayout(props) {
122
123
  setLastValidGridLayouts(gridLayouts);
123
124
  }, [gridLayouts]);
124
125
 
125
- const layoutChildren = useMemo(() => {
126
+ const [layoutChildren, pageModeViews] = useMemo(() => {
126
127
  const gridComponentsValues = Object.values(gridComponents);
127
- if (pageMode) {
128
- // This is pageMode, so we render based on the children.
129
- return children.map((child) => {
130
- const childId = child.props.id;
131
- const v = gridComponentsValues.find(el => el.uid === childId);
132
- if (!v) {
133
- // The child did not map to any view in the config, so return as-is.
134
- return child;
135
- }
136
- // The child mapped to a view in the config,
137
- // so we want to render the corresponding component as a subchild.
138
- const Component = getComponent(v.component);
139
- return React.cloneElement(child, { key: v.uid }, (
140
- <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
141
- <Component
142
- {... v.props}
143
- uuid={v.uid}
144
- coordinationScopes={v.coordinationScopes}
145
- coordinationScopesBy={v.coordinationScopesBy}
146
- theme={theme}
147
- removeGridComponent={null}
148
- />
149
- </div>
150
- ));
151
- });
152
- }
128
+
129
+ const contextValue = Object.fromEntries(gridComponentsValues.map((v) => {
130
+ const Component = getComponent(v.component);
131
+ return [v.uid, () => (
132
+ <div key={v.uid} style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
133
+ <Component
134
+ {... v.props}
135
+ uuid={v.uid}
136
+ coordinationScopes={v.coordinationScopes}
137
+ coordinationScopesBy={v.coordinationScopesBy}
138
+ theme={theme}
139
+ removeGridComponent={null}
140
+ />
141
+ </div>
142
+ )];
143
+ }));
144
+
153
145
  // Not pageMode, so no children to render.
154
146
  // We render the grid components based on the layout information.
155
- return gridComponentsValues.map((v) => {
147
+ return [gridComponentsValues.map((v) => {
156
148
  const Component = getComponent(v.component);
157
149
  const removeGridComponent = () => {
158
150
  onRemoveComponent(v.uid);
@@ -169,12 +161,15 @@ export function VitessceGridLayout(props) {
169
161
  />
170
162
  </div>
171
163
  );
172
- });
164
+ }), contextValue];
173
165
  }, [gridComponents, getComponent, onRemoveComponent, theme]);
174
166
 
175
167
  return (pageMode
176
- ? layoutChildren
177
- : ((gridLayouts && gridComponents && gridBreakpoints && gridCols) && (
168
+ ? (
169
+ <PageModeViewContext.Provider value={pageModeViews}>
170
+ {children}
171
+ </PageModeViewContext.Provider>
172
+ ) : ((gridLayouts && gridComponents && gridBreakpoints && gridCols) && (
178
173
  <>
179
174
  {style}
180
175
  <ResponsiveHeightGridLayout