@vitessce/vit-s 3.2.1 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -32233,9 +32233,9 @@ const schemePlasma = [[13, 8, 135], [16, 7, 136], [19, 7, 137], [22, 7, 138], [2
32233
32233
  function rgbSpline(spline) {
32234
32234
  return (colors2) => {
32235
32235
  const n = colors2.length;
32236
- let r2 = new Array(n);
32237
- let g = new Array(n);
32238
- let b = new Array(n);
32236
+ const r2 = new Array(n);
32237
+ const g = new Array(n);
32238
+ const b = new Array(n);
32239
32239
  let i;
32240
32240
  let color2;
32241
32241
  for (i = 0; i < n; ++i) {
@@ -32244,10 +32244,10 @@ function rgbSpline(spline) {
32244
32244
  g[i] = color2[1] || 0;
32245
32245
  b[i] = color2[2] || 0;
32246
32246
  }
32247
- r2 = spline(r2);
32248
- g = spline(g);
32249
- b = spline(b);
32250
- return (t2) => [r2(t2), g(t2), b(t2)];
32247
+ const rFunc = spline(r2);
32248
+ const gFunc = spline(g);
32249
+ const bFunc = spline(b);
32250
+ return (t2) => [rFunc(t2), gFunc(t2), bFunc(t2)];
32251
32251
  };
32252
32252
  }
32253
32253
  function basis(values2) {
@@ -35149,6 +35149,7 @@ const DataType$1 = {
35149
35149
  const FileType$1 = {
35150
35150
  // Joint file types
35151
35151
  ANNDATA_ZARR: "anndata.zarr",
35152
+ SPATIALDATA_ZARR: "spatialdata.zarr",
35152
35153
  // Atomic file types
35153
35154
  OBS_EMBEDDING_CSV: "obsEmbedding.csv",
35154
35155
  OBS_SPOTS_CSV: "obsSpots.csv",
@@ -35171,6 +35172,16 @@ const FileType$1 = {
35171
35172
  OBS_SEGMENTATIONS_ANNDATA_ZARR: "obsSegmentations.anndata.zarr",
35172
35173
  OBS_LABELS_ANNDATA_ZARR: "obsLabels.anndata.zarr",
35173
35174
  FEATURE_LABELS_ANNDATA_ZARR: "featureLabels.anndata.zarr",
35175
+ // SpatialData
35176
+ IMAGE_SPATIALDATA_ZARR: "image.spatialdata.zarr",
35177
+ LABELS_SPATIALDATA_ZARR: "labels.spatialdata.zarr",
35178
+ SHAPES_SPATIALDATA_ZARR: "shapes.spatialdata.zarr",
35179
+ OBS_FEATURE_MATRIX_SPATIALDATA_ZARR: "obsFeatureMatrix.spatialdata.zarr",
35180
+ OBS_SETS_SPATIALDATA_ZARR: "obsSets.spatialdata.zarr",
35181
+ OBS_SPOTS_SPATIALDATA_ZARR: "obsSpots.spatialdata.zarr",
35182
+ // TODO:
35183
+ // OBS_POINTS_SPATIALDATA_ZARR: 'obsPoints.spatialdata.zarr',
35184
+ // OBS_LOCATIONS_SPATIALDATA_ZARR: 'obsLocations.spatialdata.zarr',
35174
35185
  // MuData
35175
35186
  OBS_FEATURE_MATRIX_MUDATA_ZARR: "obsFeatureMatrix.mudata.zarr",
35176
35187
  OBS_SETS_MUDATA_ZARR: "obsSets.mudata.zarr",
@@ -35383,10 +35394,10 @@ const AUTO_INDEPENDENT_COORDINATION_TYPES = [
35383
35394
  CoordinationType$1.EMBEDDING_OBS_OPACITY
35384
35395
  ];
35385
35396
  const note = "This file is autogenerated by .changeset/post-changelog.mjs.";
35386
- const version = "3.2.1";
35387
- const date = "2023-09-13";
35397
+ const version = "3.3.0";
35398
+ const date = "2023-09-28";
35388
35399
  const branch = "changeset-release/main";
35389
- const hash = "a118dd03";
35400
+ const hash = "4b4a99ea";
35390
35401
  const META_VERSION = {
35391
35402
  note,
35392
35403
  version,
@@ -35632,7 +35643,13 @@ function buildConfigSchema(pluginFileTypes, pluginJointFileTypes, pluginCoordina
35632
35643
  // Wrap each value schema in z.record()
35633
35644
  fromEntries(pluginCoordinationTypes.map((ct) => [
35634
35645
  ct.name,
35635
- z.record(coordinationScopeName, ct.valueSchema.optional()).optional()
35646
+ z.record(
35647
+ // For now, assume the key type is string (though it would be
35648
+ // slightly nicer if we could use the coordinationScopeName schema here).
35649
+ // Once https://github.com/colinhacks/zod/issues/2746 gets resolved
35650
+ // then we can try that approach again, but it should not be a big deal.
35651
+ ct.valueSchema.optional()
35652
+ ).optional()
35636
35653
  ]))
35637
35654
  ).strict().describe("The coordination space stores the values for each scope of each coordination object.").optional(),
35638
35655
  layout: z.array(z.object({
@@ -35740,9 +35757,30 @@ z.object({
35740
35757
  offsetsUrl: z.string().optional(),
35741
35758
  coordinateTransformations: omeCoordinateTransformations.optional()
35742
35759
  });
35743
- z.object({
35760
+ const imageOmeZarrSchema = z.object({
35744
35761
  coordinateTransformations: omeCoordinateTransformations.optional()
35745
35762
  });
35763
+ imageOmeZarrSchema.extend({
35764
+ path: z.string()
35765
+ });
35766
+ z.object({
35767
+ path: z.string()
35768
+ });
35769
+ z.object({
35770
+ path: z.string()
35771
+ });
35772
+ z.object({
35773
+ path: z.string(),
35774
+ tablePath: z.string().optional().describe("The path to a table which annotates the spots. If available but not specified, the spot identifiers may not be aligned with associated tabular data as expected.")
35775
+ });
35776
+ annDataObsFeatureMatrix.extend({
35777
+ region: z.string().describe("The name of a region to use to filter instances (i.e., rows) in the table").optional()
35778
+ });
35779
+ z.object({
35780
+ region: z.string().describe("The name of a region to use to filter instances (i.e., rows) in the table").optional(),
35781
+ tablePath: z.string().optional().describe("The path to a table which contains the index for the set values."),
35782
+ obsSets: annDataObsSets
35783
+ });
35746
35784
  z.object({
35747
35785
  obsIndex: z.string(),
35748
35786
  obsEmbedding: z.array(z.string()).length(2)
@@ -43130,9 +43168,9 @@ After: ${JSON.stringify(coordinationValuesWithDefaults)}`);
43130
43168
  }
43131
43169
  return coordinationValuesWithDefaults;
43132
43170
  }
43133
- function createLoaders(datasets, configDescription, fileTypes, coordinationTypes) {
43171
+ function createLoaders(datasets, configDescription, fileTypes, coordinationTypes, stores) {
43134
43172
  const result = {};
43135
- const dataSources = {};
43173
+ const dataSources = new InternMap([], JSON.stringify);
43136
43174
  const defaultCoordinationValues = fromEntries(
43137
43175
  coordinationTypes.map((ct) => [ct.name, ct.defaultValue])
43138
43176
  );
@@ -43160,10 +43198,18 @@ function createLoaders(datasets, configDescription, fileTypes, coordinationTypes
43160
43198
  );
43161
43199
  const [DataSourceClass, LoaderClass] = getSourceAndLoaderFromFileType(fileType, fileTypes);
43162
43200
  const fileId = url || JSON.stringify(options);
43163
- if (!(fileId in dataSources)) {
43164
- dataSources[fileId] = new DataSourceClass({ url, requestInit: requestInit2 });
43201
+ const dataSourceName = DataSourceClass.prototype.constructor.name;
43202
+ const dataSourceKey = [fileId, dataSourceName];
43203
+ if (!dataSources.has(dataSourceKey)) {
43204
+ dataSources.set(dataSourceKey, new DataSourceClass({
43205
+ url,
43206
+ requestInit: requestInit2,
43207
+ // Optionally, pass a Zarrita store to the data source,
43208
+ // if one was mapped to this URL.
43209
+ store: stores == null ? void 0 : stores[url]
43210
+ }));
43165
43211
  }
43166
- const loader = new LoaderClass(dataSources[fileId], file);
43212
+ const loader = new LoaderClass(dataSources.get(dataSourceKey), file);
43167
43213
  if (datasetLoaders.loaders[dataType]) {
43168
43214
  datasetLoaders.loaders[dataType].set(coordinationValuesWithDefaults, loader);
43169
43215
  } else {
@@ -43699,6 +43745,38 @@ const useTitleStyles = makeStyles((theme) => ({
43699
43745
  }
43700
43746
  }
43701
43747
  }));
43748
+ function arrayToString(arr) {
43749
+ const arrF = arr.filter((item) => item !== void 0);
43750
+ if (arrF.length === 0) {
43751
+ return "";
43752
+ }
43753
+ if (arrF.length === 1) {
43754
+ return arrF[0];
43755
+ }
43756
+ return `${arr.slice(0, -1).join(", ")} and ${arr.slice(-1)}`;
43757
+ }
43758
+ const ViewTypesToText = /* @__PURE__ */ new Map([
43759
+ [ViewType$1.DESCRIPTION, "description"],
43760
+ [ViewType$1.STATUS, "status view for debugging"],
43761
+ [ViewType$1.SCATTERPLOT, "scatterplot with embeddings"],
43762
+ [ViewType$1.SPATIAL, "spatial view"],
43763
+ [ViewType$1.SPATIAL_BETA, "spatial view"],
43764
+ [ViewType$1.HEATMAP, "cell by gene heatmap"],
43765
+ [ViewType$1.LAYER_CONTROLLER, "layer controller"],
43766
+ [ViewType$1.LAYER_CONTROLLER_BETA, "layer controller"],
43767
+ [ViewType$1.GENOMIC_PROFILES, "genome browser tracks with bar plots"],
43768
+ [ViewType$1.GATING, "scatterplot of gated gene expression data"],
43769
+ [ViewType$1.FEATURE_LIST, "interactive list of features"],
43770
+ [ViewType$1.OBS_SETS, "list of potential observation sets"],
43771
+ [ViewType$1.OBS_SET_SIZES, "sizes of selected observation sets"],
43772
+ [ViewType$1.OBS_SET_FEATURE_VALUE_DISTRIBUTION, "violin plot with values"],
43773
+ [ViewType$1.FEATURE_VALUE_HISTOGRAM, "distribution of values"]
43774
+ ]);
43775
+ function getAltText(config2) {
43776
+ const componentList = config2.layout.map((c) => c.component);
43777
+ const altText = `Vitessce grid with ${componentList.length} views, including ${arrayToString(componentList.map((c) => ViewTypesToText.get(c) || "plugin view"))}.`;
43778
+ return altText;
43779
+ }
43702
43780
  const padding = 10;
43703
43781
  const margin = 5;
43704
43782
  function VitessceGrid(props) {
@@ -43719,6 +43797,7 @@ function VitessceGrid(props) {
43719
43797
  const [componentWidth] = useClosestVitessceContainerSize(containerRef);
43720
43798
  const classes = useVitessceContainerStyles();
43721
43799
  const titleClasses = useTitleStyles();
43800
+ const altText = useMemo(() => getAltText(config2), [configKey]);
43722
43801
  useEffect(() => {
43723
43802
  onResize();
43724
43803
  }, [rowHeight, onResize]);
@@ -43753,9 +43832,12 @@ function VitessceGrid(props) {
43753
43832
  {
43754
43833
  ref: containerRef,
43755
43834
  className: clsx(VITESSCE_CONTAINER, classes.vitessceContainer),
43835
+ role: "group",
43836
+ "aria-label": altText,
43756
43837
  children: layout2 ? /* @__PURE__ */ jsxRuntimeExports.jsx(
43757
43838
  VitessceGridLayout,
43758
43839
  {
43840
+ role: "group",
43759
43841
  layout: layout2,
43760
43842
  height: height2,
43761
43843
  rowHeight,
@@ -44079,6 +44161,7 @@ function createGenerateClassName(customPrefix) {
44079
44161
  function VitS(props) {
44080
44162
  const {
44081
44163
  config: config2,
44164
+ stores,
44082
44165
  rowHeight,
44083
44166
  height: height2,
44084
44167
  theme,
@@ -44186,7 +44269,8 @@ function VitS(props) {
44186
44269
  configOrWarning.datasets,
44187
44270
  configOrWarning.description,
44188
44271
  fileTypes,
44189
- coordinationTypes
44272
+ coordinationTypes,
44273
+ stores
44190
44274
  );
44191
44275
  return createViewConfigStore(loaders, configOrWarning);
44192
44276
  }
@@ -1 +1 @@
1
- {"version":3,"file":"VitS.d.ts","sourceRoot":"","sources":["../src/VitS.js"],"names":[],"mappings":"AA8BA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH;IA7ByB,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;IAGE,SAAS;IACT,SAAS;IACT,cAAc;IACd,iBAAiB;IACX,OAAO,EAA1B,IAAI,GAAC,MAAM;gBAyLrB"}
1
+ {"version":3,"file":"VitS.d.ts","sourceRoot":"","sources":["../src/VitS.js"],"names":[],"mappings":"AA8BA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH;IA7ByB,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;IAGE,SAAS;IACT,SAAS;IACT,cAAc;IACd,iBAAiB;IACX,OAAO,EAA1B,IAAI,GAAC,MAAM;gBA2LrB"}
package/dist-tsc/VitS.js CHANGED
@@ -45,7 +45,7 @@ import { createGenerateClassName } from './mui-utils.js';
45
45
  * provided by the parent.
46
46
  */
47
47
  export function VitS(props) {
48
- const { config, rowHeight, height, theme, onWarn, onConfigChange, onLoaderChange, validateConfig = true, validateOnConfigChange = false, isBounded = false, uid = null, viewTypes: viewTypesProp, fileTypes: fileTypesProp, jointFileTypes: jointFileTypesProp, coordinationTypes: coordinationTypesProp, warning, } = props;
48
+ const { config, stores, rowHeight, height, theme, onWarn, onConfigChange, onLoaderChange, validateConfig = true, validateOnConfigChange = false, isBounded = false, uid = null, viewTypes: viewTypesProp, fileTypes: fileTypesProp, jointFileTypes: jointFileTypesProp, coordinationTypes: coordinationTypesProp, warning, } = props;
49
49
  const viewTypes = useMemo(() => (viewTypesProp || []), [viewTypesProp]);
50
50
  const fileTypes = useMemo(() => (fileTypesProp || []), [fileTypesProp]);
51
51
  const jointFileTypes = useMemo(() => (jointFileTypesProp || []), [jointFileTypesProp]);
@@ -135,7 +135,7 @@ export function VitS(props) {
135
135
  // Initialize the view config and loaders in the global state.
136
136
  const createViewConfigStoreClosure = useCallback(() => {
137
137
  if (success) {
138
- const loaders = createLoaders(configOrWarning.datasets, configOrWarning.description, fileTypes, coordinationTypes);
138
+ const loaders = createLoaders(configOrWarning.datasets, configOrWarning.description, fileTypes, coordinationTypes, stores);
139
139
  return createViewConfigStore(loaders, configOrWarning);
140
140
  }
141
141
  // No config found, so clear the loaders.
@@ -1 +1 @@
1
- {"version":3,"file":"VitessceGrid.d.ts","sourceRoot":"","sources":["../src/VitessceGrid.js"],"names":[],"mappings":"AA0BA;;;;;;;;;;;GAWG;AACH;IATyB,SAAS,EAAvB,MAAM;IACQ,MAAM,EAApB,MAAM;IACQ,KAAK,EAAnB,MAAM;IACQ,MAAM,EAApB,MAAM;IACU,MAAM;IACE,SAAS,EAAjC,gBAAgB;IACQ,SAAS,EAAjC,gBAAgB;IACgB,iBAAiB,EAAjD,wBAAwB;gBAuFlC"}
1
+ {"version":3,"file":"VitessceGrid.d.ts","sourceRoot":"","sources":["../src/VitessceGrid.js"],"names":[],"mappings":"AA4BA;;;;;;;;;;;GAWG;AACH;IATyB,SAAS,EAAvB,MAAM;IACQ,MAAM,EAApB,MAAM;IACQ,KAAK,EAAnB,MAAM;IACQ,MAAM,EAApB,MAAM;IACU,MAAM;IACE,SAAS,EAAjC,gBAAgB;IACQ,SAAS,EAAjC,gBAAgB;IACgB,iBAAiB,EAAjD,wBAAwB;gBA4FlC"}
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import React, { useEffect, useCallback, } from 'react';
2
+ import React, { useEffect, useCallback, useMemo, } from 'react';
3
3
  import clsx from 'clsx';
4
4
  import { VITESSCE_CONTAINER } from './classNames.js';
5
5
  import { VitessceGridLayout } from './vitessce-grid-layout/index.js';
@@ -8,6 +8,7 @@ import { useViewConfigStoreApi, useSetViewConfig, useSetLoaders, useEmitGridResi
8
8
  import { useClosestVitessceContainerSize, } from './hooks.js';
9
9
  import { useVitessceContainerStyles } from './shared-mui/container.js';
10
10
  import { useTitleStyles } from './title-styles.js';
11
+ import { getAltText } from './generate-alt-text.js';
11
12
  const padding = 10;
12
13
  const margin = 5;
13
14
  /**
@@ -29,6 +30,7 @@ export default function VitessceGrid(props) {
29
30
  const [componentWidth] = useClosestVitessceContainerSize(containerRef);
30
31
  const classes = useVitessceContainerStyles();
31
32
  const titleClasses = useTitleStyles();
33
+ const altText = useMemo(() => getAltText(config), [configKey]);
32
34
  // When the row height has changed, publish a GRID_RESIZE event.
33
35
  useEffect(() => {
34
36
  onResize();
@@ -59,5 +61,5 @@ export default function VitessceGrid(props) {
59
61
  setLoaders(newLoaders);
60
62
  // eslint-disable-next-line react-hooks/exhaustive-deps
61
63
  }, [success, configKey]);
62
- return (_jsx("div", { ref: containerRef, className: clsx(VITESSCE_CONTAINER, classes.vitessceContainer), children: layout ? (_jsx(VitessceGridLayout, { layout: layout, height: height, rowHeight: rowHeight, theme: theme, viewTypes: viewTypes, draggableHandle: titleClasses.title, margin: margin, padding: padding, onRemoveComponent: removeComponent, onLayoutChange: changeLayoutPostMount, isBounded: isBounded, onResize: onResize, onResizeStop: onResize })) : null }));
64
+ return (_jsx("div", { ref: containerRef, className: clsx(VITESSCE_CONTAINER, classes.vitessceContainer), role: "group", "aria-label": altText, children: layout ? (_jsx(VitessceGridLayout, { role: "group", layout: layout, height: height, rowHeight: rowHeight, theme: theme, viewTypes: viewTypes, draggableHandle: titleClasses.title, margin: margin, padding: padding, onRemoveComponent: removeComponent, onLayoutChange: changeLayoutPostMount, isBounded: isBounded, onResize: onResize, onResizeStop: onResize })) : null }));
63
65
  }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Method for converting an array with strings to a natural sentence,
3
+ * concatenating the items with commans, and the last item with 'and'.
4
+ * @param {Array} arr Array with text items.
5
+ * @returns {string} Natural language sentence in the form of 'a, b and c', where a/b/c are items.
6
+ */
7
+ export function arrayToString(arr: any[]): string;
8
+ /**
9
+ * Method for converting an array with strings to a natural sentence,
10
+ * concatenating the items with commans, and the last item with 'and'.
11
+ * @param {Object} config Vitessce config.
12
+ * @returns {string} Natural language sentence describing config (alt-text).
13
+ */
14
+ export function getAltText(config: Object): string;
15
+ /**
16
+ * Mapping of ViewType to natural language description of ViewType.
17
+ */
18
+ export const ViewTypesToText: Map<string, string>;
19
+ //# sourceMappingURL=generate-alt-text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-alt-text.d.ts","sourceRoot":"","sources":["../src/generate-alt-text.js"],"names":[],"mappings":"AAEA;;;;;KAKK;AACL,2CAFe,MAAM,CAWpB;AAuBD;;;;;KAKK;AACL,mCAHa,MAAM,GACJ,MAAM,CAMpB;AA/BD;;KAEK;AACL,kDAgBG"}
@@ -0,0 +1,48 @@
1
+ import { ViewType } from '@vitessce/constants-internal';
2
+ /**
3
+ * Method for converting an array with strings to a natural sentence,
4
+ * concatenating the items with commans, and the last item with 'and'.
5
+ * @param {Array} arr Array with text items.
6
+ * @returns {string} Natural language sentence in the form of 'a, b and c', where a/b/c are items.
7
+ */
8
+ export function arrayToString(arr) {
9
+ const arrF = arr.filter(item => item !== undefined);
10
+ if (arrF.length === 0) {
11
+ return '';
12
+ }
13
+ if (arrF.length === 1) {
14
+ return arrF[0];
15
+ }
16
+ return `${arr.slice(0, -1).join(', ')} and ${arr.slice(-1)}`;
17
+ }
18
+ /**
19
+ * Mapping of ViewType to natural language description of ViewType.
20
+ */
21
+ export const ViewTypesToText = new Map([
22
+ [ViewType.DESCRIPTION, 'description'],
23
+ [ViewType.STATUS, 'status view for debugging'],
24
+ [ViewType.SCATTERPLOT, 'scatterplot with embeddings'],
25
+ [ViewType.SPATIAL, 'spatial view'],
26
+ [ViewType.SPATIAL_BETA, 'spatial view'],
27
+ [ViewType.HEATMAP, 'cell by gene heatmap'],
28
+ [ViewType.LAYER_CONTROLLER, 'layer controller'],
29
+ [ViewType.LAYER_CONTROLLER_BETA, 'layer controller'],
30
+ [ViewType.GENOMIC_PROFILES, 'genome browser tracks with bar plots'],
31
+ [ViewType.GATING, 'scatterplot of gated gene expression data'],
32
+ [ViewType.FEATURE_LIST, 'interactive list of features'],
33
+ [ViewType.OBS_SETS, 'list of potential observation sets'],
34
+ [ViewType.OBS_SET_SIZES, 'sizes of selected observation sets'],
35
+ [ViewType.OBS_SET_FEATURE_VALUE_DISTRIBUTION, 'violin plot with values'],
36
+ [ViewType.FEATURE_VALUE_HISTOGRAM, 'distribution of values'],
37
+ ]);
38
+ /**
39
+ * Method for converting an array with strings to a natural sentence,
40
+ * concatenating the items with commans, and the last item with 'and'.
41
+ * @param {Object} config Vitessce config.
42
+ * @returns {string} Natural language sentence describing config (alt-text).
43
+ */
44
+ export function getAltText(config) {
45
+ const componentList = config.layout.map(c => c.component);
46
+ const altText = `Vitessce grid with ${componentList.length} views, including ${arrayToString(componentList.map(c => ViewTypesToText.get(c) || 'plugin view'))}.`;
47
+ return altText;
48
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=generate-alt-text.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-alt-text.test.d.ts","sourceRoot":"","sources":["../src/generate-alt-text.test.js"],"names":[],"mappings":""}
@@ -0,0 +1,67 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ViewType } from '@vitessce/constants-internal';
3
+ import { arrayToString, ViewTypesToText, getAltText } from './generate-alt-text.js';
4
+ describe('Tests for generating an alt text from the vitessce config', () => {
5
+ describe('Tests for arrayToString', () => {
6
+ it('should return empty string for empty array', () => {
7
+ const arr = [];
8
+ const str = arrayToString(arr);
9
+ expect(str).toEqual('');
10
+ });
11
+ it('should return the element if the araay only has one element', () => {
12
+ const arr = ['one'];
13
+ const str = arrayToString(arr);
14
+ expect(str).toEqual('one');
15
+ });
16
+ it('should return the elements concatenated with "and" when the array has two elements', () => {
17
+ const arr = ['one', 'two'];
18
+ const str = arrayToString(arr);
19
+ expect(str).toEqual('one and two');
20
+ });
21
+ it('should return the elements concatenated with ", " and the last two elements with "and" when the array has more than two elements', () => {
22
+ const arr = ['one', 'two', 'three', 'four'];
23
+ const str = arrayToString(arr);
24
+ expect(str).toEqual('one, two, three and four');
25
+ });
26
+ });
27
+ describe('Tests for (const) ViewTypesToText', () => {
28
+ it('should have a natural language mapping for every defined viewtype', () => {
29
+ expect(ViewTypesToText.length).toEqual(ViewType.length);
30
+ });
31
+ });
32
+ describe('Tests for getAltTest', () => {
33
+ const config = {
34
+ coordinationSpace: {},
35
+ datasets: {},
36
+ description: 'Spatial organization of the somatosensory cortex revealed by osmFISH',
37
+ initStrategy: 'auto',
38
+ layout: [
39
+ { component: 'description', props: { description: 'Codeluppi et al., Nature Methods 2018: Spatial organization of the somatosensory cortex revealed by osmFISH' }, x: 0, y: 0, w: 2 },
40
+ { component: 'layerController', x: 0, y: 1, w: 2, h: 4 },
41
+ { component: 'status', x: 0, y: 5, w: 2, h: 1 },
42
+ { component: 'spatial', props: { channelNamesVisible: true }, x: 2, y: 0, w: 4 },
43
+ { component: 'featureList', x: 9, y: 0, w: 3, h: 2 },
44
+ { component: 'obsSets', x: 9, y: 3, w: 3, h: 2 },
45
+ { component: 'heatmap', props: { transpose: true }, x: 2, y: 4, w: 5 },
46
+ { component: 'obsSetFeatureValueDistribution', x: 7, y: 4, w: 5, h: 2 },
47
+ { component: 'scatterplot', x: 6, y: 0, w: 3, h: 2 },
48
+ { component: 'scatterplot', x: 6, y: 2, w: 3, h: 2 },
49
+ ],
50
+ name: 'Codeluppi et al., Nature Methods 2018',
51
+ uid: 'A',
52
+ version: '1.0.16',
53
+ };
54
+ const altText = getAltText(config);
55
+ it('should return a string', () => {
56
+ expect(typeof altText).toEqual('string');
57
+ });
58
+ it('should contain the number of views', () => {
59
+ const hasNViews = altText.includes('10');
60
+ expect(hasNViews).toEqual(true);
61
+ });
62
+ it('should contain mappings', () => {
63
+ const hasHeatmap = altText.includes('heatmap') && altText.includes('spatial') && altText.includes('description') && altText.includes('scatterplot');
64
+ expect(hasHeatmap).toEqual(true);
65
+ });
66
+ });
67
+ });
@@ -62,7 +62,9 @@ export function useMultiCoordinationScopes(parameter: any, coordinationScopes: a
62
62
  export function useMultiCoordinationScopesNonNull(parameter: any, coordinationScopes: any): any;
63
63
  export function useMultiCoordinationScopesSecondary(parameter: any, byType: any, coordinationScopes: any, coordinationScopesBy: any): any;
64
64
  export function useMultiCoordinationScopesSecondaryNonNull(parameter: any, byType: any, coordinationScopes: any, coordinationScopesBy: any): any;
65
- export function useMultiCoordinationValues(parameter: any, coordinationScopes: any): any;
65
+ export function useMultiCoordinationValues(parameter: any, coordinationScopes: any): {
66
+ [key: string]: any;
67
+ };
66
68
  /**
67
69
  * Get a mapping from dataset coordination scopes to dataset UIDs.
68
70
  * @param {object} coordinationScopes The coordination scope mapping object for a view.
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/state/hooks.js"],"names":[],"mappings":"AAkCA;;;;;;GAMG;AACH,oEAFa,MAAM,GAAC,SAAS,CAoB5B;AAED;;;;;;;GAOG;AACH,mGAFa,MAAM,GAAC,SAAS,CAoB5B;AAED;;;;;GAKG;AACH,6CAJW,MAAM,4BAEJ,MAAM,GAAC,SAAS,CAI5B;AAED;;;;;;GAMG;AACH,+CALW,MAAM,oFAGJ,MAAM,GAAC,SAAS,CAc5B;AA2MD;;;;;;;;;GASG;AACH,mDALW,MAAM,EAAE,sBACR,MAAM,GAEJ,MAAM,CAclB;AAED;;;;;;;;;;;;;;;GAeG;AACH,4CATW,MAAM,EAAE,sBACR,MAAM,SAqChB;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,SA6EhB;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,SAYhB;AA6BD;;;;;;;;;;;;;;;GAeG;AACH,qDATW,MAAM,EAAE,sBACR,MAAM,SAkChB;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,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;AAnhCD;;;;+QAA0D;AAC1D,sFAA0D;AAC1D;;;;;EAAgE;AAOhE;;;;+QAAwD;AACxD,qFAAwD;AAmGjD,yFA8FJ;AAOI,0FAFM,MAAM,CAOlB;AAcM,iDAUJ"}
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/state/hooks.js"],"names":[],"mappings":"AAkCA;;;;;;GAMG;AACH,oEAFa,MAAM,GAAC,SAAS,CAoB5B;AAED;;;;;;;GAOG;AACH,mGAFa,MAAM,GAAC,SAAS,CAoB5B;AAED;;;;;GAKG;AACH,6CAJW,MAAM,4BAEJ,MAAM,GAAC,SAAS,CAI5B;AAED;;;;;;GAMG;AACH,+CALW,MAAM,oFAGJ,MAAM,GAAC,SAAS,CAc5B;AA2MD;;;;;;;;;GASG;AACH,mDALW,MAAM,EAAE,sBACR,MAAM,GAEJ,MAAM,CAclB;AAED;;;;;;;;;;;;;;;GAeG;AACH,4CATW,MAAM,EAAE,sBACR,MAAM,SAqChB;AAED,yFAKC;AAED,gGAqBC;AAED,0IA4BC;AAED,iJAkEC;AAED;;EAmBC;AAED;;;;GAIG;AACH,mDAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;;;;;;;;;;GAaG;AACH,mDAXW,MAAM,EAAE,sBACR,MAAM,wBACN,MAAM,UAEN,MAAM,SA6EhB;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,SAYhB;AA6BD;;;;;;;;;;;;;;;GAeG;AACH,qDATW,MAAM,EAAE,sBACR,MAAM,SAkChB;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,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;AAnhCD;;;;+QAA0D;AAC1D,sFAA0D;AAC1D;;;;;EAAgE;AAOhE;;;;+QAAwD;AACxD,qFAAwD;AAmGjD,yFA8FJ;AAOI,0FAFM,MAAM,CAOlB;AAcM,iDAUJ"}
@@ -3,11 +3,12 @@ export function useRowHeight(config: any, initialRowHeight: any, height: any, ma
3
3
  * Create a mapping from dataset ID to loader objects by data type.
4
4
  * @param {object[]} datasets The datasets array from the view config.
5
5
  * @param {string} configDescription The top-level description in the
6
+ * view config.
6
7
  * @param {PluginFileType[]} fileTypes
7
8
  * @param {PluginCoordinationType[]} coordinationTypes
8
- * view config.
9
+ * @param {object} stores Optional mapping from URLs to Zarrita stores.
9
10
  * @returns {object} Mapping from dataset ID to data type to loader
10
11
  * instance.
11
12
  */
12
- export function createLoaders(datasets: object[], configDescription: string, fileTypes: PluginFileType[], coordinationTypes: PluginCoordinationType[]): object;
13
+ export function createLoaders(datasets: object[], configDescription: string, fileTypes: PluginFileType[], coordinationTypes: PluginCoordinationType[], stores: object): object;
13
14
  //# sourceMappingURL=vitessce-grid-utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"vitessce-grid-utils.d.ts","sourceRoot":"","sources":["../src/vitessce-grid-utils.js"],"names":[],"mappings":"AA0CA,gHA6CC;AA4BD;;;;;;;;;GASG;AACH,wCARW,MAAM,EAAE,qBACR,MAAM,aACN,gBAAgB,qBAChB,wBAAwB,GAEtB,MAAM,CAiDlB"}
1
+ {"version":3,"file":"vitessce-grid-utils.d.ts","sourceRoot":"","sources":["../src/vitessce-grid-utils.js"],"names":[],"mappings":"AA0CA,gHA6CC;AA4BD;;;;;;;;;;GAUG;AACH,wCATW,MAAM,EAAE,qBACR,MAAM,aAEN,gBAAgB,qBAChB,wBAAwB,UACxB,MAAM,GACJ,MAAM,CA4DlB"}
@@ -99,15 +99,16 @@ function withDefaults(coordinationValues, dataType, fileType, datasetUid, defaul
99
99
  * Create a mapping from dataset ID to loader objects by data type.
100
100
  * @param {object[]} datasets The datasets array from the view config.
101
101
  * @param {string} configDescription The top-level description in the
102
+ * view config.
102
103
  * @param {PluginFileType[]} fileTypes
103
104
  * @param {PluginCoordinationType[]} coordinationTypes
104
- * view config.
105
+ * @param {object} stores Optional mapping from URLs to Zarrita stores.
105
106
  * @returns {object} Mapping from dataset ID to data type to loader
106
107
  * instance.
107
108
  */
108
- export function createLoaders(datasets, configDescription, fileTypes, coordinationTypes) {
109
+ export function createLoaders(datasets, configDescription, fileTypes, coordinationTypes, stores) {
109
110
  const result = {};
110
- const dataSources = {};
111
+ const dataSources = new InternMap([], JSON.stringify);
111
112
  const defaultCoordinationValues = fromEntries(coordinationTypes.map(ct => ([ct.name, ct.defaultValue])));
112
113
  datasets.forEach((dataset) => {
113
114
  const datasetLoaders = {
@@ -120,12 +121,23 @@ export function createLoaders(datasets, configDescription, fileTypes, coordinati
120
121
  const dataType = getDataTypeFromFileType(fileType, fileTypes);
121
122
  const coordinationValuesWithDefaults = withDefaults(coordinationValues, dataType, fileType, dataset.uid, defaultCoordinationValues);
122
123
  const [DataSourceClass, LoaderClass] = getSourceAndLoaderFromFileType(fileType, fileTypes);
123
- // Create _one_ DataSourceClass instance per URL. Derived loaders share this object.
124
+ // Create _one_ DataSourceClass instance per (URL, DataSource class name) pair.
125
+ // Derived loaders share this object.
124
126
  const fileId = url || JSON.stringify(options);
125
- if (!(fileId in dataSources)) {
126
- dataSources[fileId] = new DataSourceClass({ url, requestInit });
127
+ // The class name might be minified but that should not matter;
128
+ // we just need a string that is unique to the class for the key.
129
+ const dataSourceName = DataSourceClass.prototype.constructor.name;
130
+ const dataSourceKey = [fileId, dataSourceName];
131
+ if (!dataSources.has(dataSourceKey)) {
132
+ dataSources.set(dataSourceKey, new DataSourceClass({
133
+ url,
134
+ requestInit,
135
+ // Optionally, pass a Zarrita store to the data source,
136
+ // if one was mapped to this URL.
137
+ store: stores?.[url],
138
+ }));
127
139
  }
128
- const loader = new LoaderClass(dataSources[fileId], file);
140
+ const loader = new LoaderClass(dataSources.get(dataSourceKey), file);
129
141
  if (datasetLoaders.loaders[dataType]) {
130
142
  datasetLoaders.loaders[dataType].set(coordinationValuesWithDefaults, loader);
131
143
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitessce/vit-s",
3
- "version": "3.2.1",
3
+ "version": "3.3.0",
4
4
  "author": "Gehlenborg Lab",
5
5
  "homepage": "http://vitessce.io",
6
6
  "repository": {
@@ -29,10 +29,10 @@
29
29
  "uuid": "^9.0.0",
30
30
  "zustand": "^3.5.10",
31
31
  "react-aria": "^3.28.0",
32
- "@vitessce/constants-internal": "3.2.1",
33
- "@vitessce/schemas": "3.2.1",
34
- "@vitessce/plugins": "3.2.1",
35
- "@vitessce/utils": "3.2.1"
32
+ "@vitessce/constants-internal": "3.3.0",
33
+ "@vitessce/plugins": "3.3.0",
34
+ "@vitessce/schemas": "3.3.0",
35
+ "@vitessce/utils": "3.3.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@testing-library/jest-dom": "^5.16.4",
package/src/VitS.js CHANGED
@@ -63,6 +63,7 @@ import { createGenerateClassName } from './mui-utils.js';
63
63
  export function VitS(props) {
64
64
  const {
65
65
  config,
66
+ stores,
66
67
  rowHeight,
67
68
  height,
68
69
  theme,
@@ -197,6 +198,7 @@ export function VitS(props) {
197
198
  configOrWarning.description,
198
199
  fileTypes,
199
200
  coordinationTypes,
201
+ stores,
200
202
  );
201
203
  return createViewConfigStore(loaders, configOrWarning);
202
204
  }
@@ -1,6 +1,7 @@
1
1
  import React, {
2
2
  useEffect,
3
3
  useCallback,
4
+ useMemo,
4
5
  } from 'react';
5
6
  import clsx from 'clsx';
6
7
  import { VITESSCE_CONTAINER } from './classNames.js';
@@ -20,6 +21,7 @@ import {
20
21
  } from './hooks.js';
21
22
  import { useVitessceContainerStyles } from './shared-mui/container.js';
22
23
  import { useTitleStyles } from './title-styles.js';
24
+ import { getAltText } from './generate-alt-text.js';
23
25
 
24
26
  const padding = 10;
25
27
  const margin = 5;
@@ -58,6 +60,8 @@ export default function VitessceGrid(props) {
58
60
  const classes = useVitessceContainerStyles();
59
61
  const titleClasses = useTitleStyles();
60
62
 
63
+ const altText = useMemo(() => getAltText(config), [configKey]);
64
+
61
65
  // When the row height has changed, publish a GRID_RESIZE event.
62
66
  useEffect(() => {
63
67
  onResize();
@@ -101,9 +105,12 @@ export default function VitessceGrid(props) {
101
105
  <div
102
106
  ref={containerRef}
103
107
  className={clsx(VITESSCE_CONTAINER, classes.vitessceContainer)}
108
+ role="group"
109
+ aria-label={altText}
104
110
  >
105
111
  {layout ? (
106
112
  <VitessceGridLayout
113
+ role="group"
107
114
  layout={layout}
108
115
  height={height}
109
116
  rowHeight={rowHeight}
@@ -0,0 +1,51 @@
1
+ import { ViewType } from '@vitessce/constants-internal';
2
+
3
+ /**
4
+ * Method for converting an array with strings to a natural sentence,
5
+ * concatenating the items with commans, and the last item with 'and'.
6
+ * @param {Array} arr Array with text items.
7
+ * @returns {string} Natural language sentence in the form of 'a, b and c', where a/b/c are items.
8
+ */
9
+ export function arrayToString(arr) {
10
+ const arrF = arr.filter(item => item !== undefined);
11
+ if (arrF.length === 0) {
12
+ return '';
13
+ }
14
+ if (arrF.length === 1) {
15
+ return arrF[0];
16
+ }
17
+ return `${arr.slice(0, -1).join(', ')} and ${arr.slice(-1)}`;
18
+ }
19
+
20
+ /**
21
+ * Mapping of ViewType to natural language description of ViewType.
22
+ */
23
+ export const ViewTypesToText = new Map([
24
+ [ViewType.DESCRIPTION, 'description'],
25
+ [ViewType.STATUS, 'status view for debugging'],
26
+ [ViewType.SCATTERPLOT, 'scatterplot with embeddings'],
27
+ [ViewType.SPATIAL, 'spatial view'],
28
+ [ViewType.SPATIAL_BETA, 'spatial view'],
29
+ [ViewType.HEATMAP, 'cell by gene heatmap'],
30
+ [ViewType.LAYER_CONTROLLER, 'layer controller'],
31
+ [ViewType.LAYER_CONTROLLER_BETA, 'layer controller'],
32
+ [ViewType.GENOMIC_PROFILES, 'genome browser tracks with bar plots'],
33
+ [ViewType.GATING, 'scatterplot of gated gene expression data'],
34
+ [ViewType.FEATURE_LIST, 'interactive list of features'],
35
+ [ViewType.OBS_SETS, 'list of potential observation sets'],
36
+ [ViewType.OBS_SET_SIZES, 'sizes of selected observation sets'],
37
+ [ViewType.OBS_SET_FEATURE_VALUE_DISTRIBUTION, 'violin plot with values'],
38
+ [ViewType.FEATURE_VALUE_HISTOGRAM, 'distribution of values'],
39
+ ]);
40
+
41
+ /**
42
+ * Method for converting an array with strings to a natural sentence,
43
+ * concatenating the items with commans, and the last item with 'and'.
44
+ * @param {Object} config Vitessce config.
45
+ * @returns {string} Natural language sentence describing config (alt-text).
46
+ */
47
+ export function getAltText(config) {
48
+ const componentList = config.layout.map(c => c.component);
49
+ const altText = `Vitessce grid with ${componentList.length} views, including ${arrayToString(componentList.map(c => ViewTypesToText.get(c) || 'plugin view'))}.`;
50
+ return altText;
51
+ }
@@ -0,0 +1,73 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ViewType } from '@vitessce/constants-internal';
3
+ import { arrayToString, ViewTypesToText, getAltText } from './generate-alt-text.js';
4
+
5
+ describe('Tests for generating an alt text from the vitessce config', () => {
6
+ describe('Tests for arrayToString', () => {
7
+ it('should return empty string for empty array', () => {
8
+ const arr = [];
9
+ const str = arrayToString(arr);
10
+ expect(str).toEqual('');
11
+ });
12
+
13
+ it('should return the element if the araay only has one element', () => {
14
+ const arr = ['one'];
15
+ const str = arrayToString(arr);
16
+ expect(str).toEqual('one');
17
+ });
18
+
19
+ it('should return the elements concatenated with "and" when the array has two elements', () => {
20
+ const arr = ['one', 'two'];
21
+ const str = arrayToString(arr);
22
+ expect(str).toEqual('one and two');
23
+ });
24
+
25
+ it('should return the elements concatenated with ", " and the last two elements with "and" when the array has more than two elements', () => {
26
+ const arr = ['one', 'two', 'three', 'four'];
27
+ const str = arrayToString(arr);
28
+ expect(str).toEqual('one, two, three and four');
29
+ });
30
+ });
31
+
32
+ describe('Tests for (const) ViewTypesToText', () => {
33
+ it('should have a natural language mapping for every defined viewtype', () => {
34
+ expect(ViewTypesToText.length).toEqual(ViewType.length);
35
+ });
36
+ });
37
+
38
+ describe('Tests for getAltTest', () => {
39
+ const config = {
40
+ coordinationSpace: {},
41
+ datasets: {},
42
+ description: 'Spatial organization of the somatosensory cortex revealed by osmFISH',
43
+ initStrategy: 'auto',
44
+ layout: [
45
+ { component: 'description', props: { description: 'Codeluppi et al., Nature Methods 2018: Spatial organization of the somatosensory cortex revealed by osmFISH' }, x: 0, y: 0, w: 2 },
46
+ { component: 'layerController', x: 0, y: 1, w: 2, h: 4 },
47
+ { component: 'status', x: 0, y: 5, w: 2, h: 1 },
48
+ { component: 'spatial', props: { channelNamesVisible: true }, x: 2, y: 0, w: 4 },
49
+ { component: 'featureList', x: 9, y: 0, w: 3, h: 2 },
50
+ { component: 'obsSets', x: 9, y: 3, w: 3, h: 2 },
51
+ { component: 'heatmap', props: { transpose: true }, x: 2, y: 4, w: 5 },
52
+ { component: 'obsSetFeatureValueDistribution', x: 7, y: 4, w: 5, h: 2 },
53
+ { component: 'scatterplot', x: 6, y: 0, w: 3, h: 2 },
54
+ { component: 'scatterplot', x: 6, y: 2, w: 3, h: 2 },
55
+ ],
56
+ name: 'Codeluppi et al., Nature Methods 2018',
57
+ uid: 'A',
58
+ version: '1.0.16',
59
+ };
60
+ const altText = getAltText(config);
61
+ it('should return a string', () => {
62
+ expect(typeof altText).toEqual('string');
63
+ });
64
+ it('should contain the number of views', () => {
65
+ const hasNViews = altText.includes('10');
66
+ expect(hasNViews).toEqual(true);
67
+ });
68
+ it('should contain mappings', () => {
69
+ const hasHeatmap = altText.includes('heatmap') && altText.includes('spatial') && altText.includes('description') && altText.includes('scatterplot');
70
+ expect(hasHeatmap).toEqual(true);
71
+ });
72
+ });
73
+ });
@@ -117,15 +117,16 @@ function withDefaults(
117
117
  * Create a mapping from dataset ID to loader objects by data type.
118
118
  * @param {object[]} datasets The datasets array from the view config.
119
119
  * @param {string} configDescription The top-level description in the
120
+ * view config.
120
121
  * @param {PluginFileType[]} fileTypes
121
122
  * @param {PluginCoordinationType[]} coordinationTypes
122
- * view config.
123
+ * @param {object} stores Optional mapping from URLs to Zarrita stores.
123
124
  * @returns {object} Mapping from dataset ID to data type to loader
124
125
  * instance.
125
126
  */
126
- export function createLoaders(datasets, configDescription, fileTypes, coordinationTypes) {
127
+ export function createLoaders(datasets, configDescription, fileTypes, coordinationTypes, stores) {
127
128
  const result = {};
128
- const dataSources = {};
129
+ const dataSources = new InternMap([], JSON.stringify);
129
130
  const defaultCoordinationValues = fromEntries(
130
131
  coordinationTypes.map(ct => ([ct.name, ct.defaultValue])),
131
132
  );
@@ -152,12 +153,23 @@ export function createLoaders(datasets, configDescription, fileTypes, coordinati
152
153
  defaultCoordinationValues,
153
154
  );
154
155
  const [DataSourceClass, LoaderClass] = getSourceAndLoaderFromFileType(fileType, fileTypes);
155
- // Create _one_ DataSourceClass instance per URL. Derived loaders share this object.
156
+ // Create _one_ DataSourceClass instance per (URL, DataSource class name) pair.
157
+ // Derived loaders share this object.
156
158
  const fileId = url || JSON.stringify(options);
157
- if (!(fileId in dataSources)) {
158
- dataSources[fileId] = new DataSourceClass({ url, requestInit });
159
+ // The class name might be minified but that should not matter;
160
+ // we just need a string that is unique to the class for the key.
161
+ const dataSourceName = DataSourceClass.prototype.constructor.name;
162
+ const dataSourceKey = [fileId, dataSourceName];
163
+ if (!dataSources.has(dataSourceKey)) {
164
+ dataSources.set(dataSourceKey, new DataSourceClass({
165
+ url,
166
+ requestInit,
167
+ // Optionally, pass a Zarrita store to the data source,
168
+ // if one was mapped to this URL.
169
+ store: stores?.[url],
170
+ }));
159
171
  }
160
- const loader = new LoaderClass(dataSources[fileId], file);
172
+ const loader = new LoaderClass(dataSources.get(dataSourceKey), file);
161
173
  if (datasetLoaders.loaders[dataType]) {
162
174
  datasetLoaders.loaders[dataType].set(coordinationValuesWithDefaults, loader);
163
175
  } else {