@tissuumaps/core 0.1.0-beta.0 → 0.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @tissuumaps/core
2
2
 
3
+ ## 0.1.0-beta.2
4
+
5
+ ### Minor Changes
6
+
7
+ - [#296](https://github.com/TissUUmaps/TissUUmaps/pull/296) [`15a214a`](https://github.com/TissUUmaps/TissUUmaps/commit/15a214a7950a1e9aa605ca5445cc4273b826ca2d) Thanks [@jwindhager](https://github.com/jwindhager)! - `immer` is declared as an optional peer dependency ([#277](https://github.com/TissUUmaps/TissUUmaps/issues/277)), since the store types depend on it through `zustand/immer`.
8
+
9
+ ## 0.1.0-beta.1
10
+
11
+ ### Minor Changes
12
+
13
+ - [#259](https://github.com/TissUUmaps/TissUUmaps/pull/259) [`62b79c2`](https://github.com/TissUUmaps/TissUUmaps/commit/62b79c2218ea5419972553b43cc217084d9a9d23) Thanks [@cavenel](https://github.com/cavenel)! - Plugins can show the settings of an image, labels, points or shapes: `appStore.getState().showImageSettings(imageId)` brings the Images panel to the front and expands the image's settings, and `showLabelsSettings`, `showPointsSettings` and `showShapesSettings` do the same for the other panels. Adds these actions and the `imageSettingsRequest`, `labelsSettingsRequest`, `pointsSettingsRequest` and `shapesSettingsRequest` app store state.
14
+
15
+ - [#253](https://github.com/TissUUmaps/TissUUmaps/pull/253) [`8acd194`](https://github.com/TissUUmaps/TissUUmaps/commit/8acd1946245c89c961af2857e13ae373bc256d62) Thanks [@jwindhager](https://github.com/jwindhager)! - `TableData.loadUniqueValueCounts` and `TableData.loadValueRange` are now optional: table data only implements them if it can determine the counts or the range more cheaply than by loading the column (e.g. from file metadata). Callers use the new `TableUtils.loadUniqueValueCounts` and `TableUtils.loadValueRange`, which fall back to the loaded column values (see `TableUtils.computeValueRange`). CSV tables no longer implement either method, and Parquet and hierarchical (AnnData) tables no longer implement `loadUniqueValueCounts`. Unless the table data determines them itself, the app now computes unique value counts and value ranges from the column values it has already loaded, instead of loading the column again.
16
+
17
+ - [#253](https://github.com/TissUUmaps/TissUUmaps/pull/253) [`8acd194`](https://github.com/TissUUmaps/TissUUmaps/commit/8acd1946245c89c961af2857e13ae373bc256d62) Thanks [@jwindhager](https://github.com/jwindhager)! - Group-by palettes (colors and markers without a map) now assign their values by group position, in the order the groups first appear in the table column, instead of by hashing the group names; random colors are still hashed. Projects that group by a palette show different colors or markers than before. `ConfigUtils.createGroupValueGetter` takes the ordered groups alongside the palette, and the new `TableUtils.loadGroupCounts` lists them.
18
+
19
+ - [#252](https://github.com/TissUUmaps/TissUUmaps/pull/252) [`cb49f75`](https://github.com/TissUUmaps/TissUUmaps/commit/cb49f75bb87c17d06198562d53e4ab17cd89aaf5) Thanks [@jwindhager](https://github.com/jwindhager)! - Plugins are no longer mounted when registered: a menu in the tab bar lists the registered plugins and mounts the one picked, and closing a plugin's panel only unmounts it. The menu can also load third-party plugins, ES modules whose default export is the plugin, from a local file or a URL, absolute or relative to the page. Third-party plugins should both default-export the plugin and register it through `window.tissuumaps`, so that the same file can be loaded through the menu or included in the page. Adds `PluginRegistry.mountPlugin`/`unmountPlugin`.
20
+
3
21
  ## 0.1.0-beta.0
4
22
 
5
23
  ### Minor Changes
package/dist/index.d.ts CHANGED
@@ -97,6 +97,34 @@ export declare type AppStoreActions = {
97
97
  * none
98
98
  */
99
99
  setHighlightedItemGroup: (highlightedItemGroup: HighlightedItemGroup | null) => void;
100
+ /**
101
+ * Shows the settings of an image: the Images panel is brought to the front
102
+ * and the settings are expanded
103
+ *
104
+ * @param imageId - ID of the image whose settings to show
105
+ */
106
+ showImageSettings: (imageId: string) => void;
107
+ /**
108
+ * Shows the settings of labels: the Labels panel is brought to the front
109
+ * and the settings are expanded
110
+ *
111
+ * @param labelsId - ID of the labels whose settings to show
112
+ */
113
+ showLabelsSettings: (labelsId: string) => void;
114
+ /**
115
+ * Shows the settings of points: the Points panel is brought to the front
116
+ * and the settings are expanded
117
+ *
118
+ * @param pointsId - ID of the points whose settings to show
119
+ */
120
+ showPointsSettings: (pointsId: string) => void;
121
+ /**
122
+ * Shows the settings of shapes: the Shapes panel is brought to the front
123
+ * and the settings are expanded
124
+ *
125
+ * @param shapesId - ID of the shapes whose settings to show
126
+ */
127
+ showShapesSettings: (shapesId: string) => void;
100
128
  /**
101
129
  * Registers an image data provider
102
130
  *
@@ -172,8 +200,9 @@ export declare type AppStoreState = {
172
200
  /** The registered table data providers, by data source type */
173
201
  tableDataProviders: Map<string, TableDataProvider<TableDataSource, TableData>>;
174
202
  /**
175
- * The registered plugins, by plugin ID, each as its human-readable name
176
- * together with the element its user interface is mounted into, if it has one
203
+ * The registered plugins, by plugin ID, each as its human-readable name,
204
+ * whether it has a user interface that can be mounted, and the element its
205
+ * user interface is mounted into while it is mounted
177
206
  *
178
207
  * Written by the plugin registry, which owns the plugin lifecycle and keeps
179
208
  * the plugin objects themselves to itself, so that nothing a plugin owns ends
@@ -181,8 +210,49 @@ export declare type AppStoreState = {
181
210
  */
182
211
  plugins: Map<string, {
183
212
  name: string;
213
+ mountable: boolean;
184
214
  container?: HTMLElement;
185
215
  }>;
216
+ /**
217
+ * The latest request to show the settings of an image, or `null` if there has
218
+ * been none
219
+ *
220
+ * Set by {@link AppStoreActions.showImageSettings} to a new object on every call, so
221
+ * that a repeated request for the same image is a change too.
222
+ */
223
+ imageSettingsRequest: {
224
+ imageId: string;
225
+ } | null;
226
+ /**
227
+ * The latest request to show the settings of labels, or `null` if there has
228
+ * been none
229
+ *
230
+ * Set by {@link AppStoreActions.showLabelsSettings} to a new object on every call, so
231
+ * that a repeated request for the same labels is a change too.
232
+ */
233
+ labelsSettingsRequest: {
234
+ labelsId: string;
235
+ } | null;
236
+ /**
237
+ * The latest request to show the settings of points, or `null` if there has
238
+ * been none
239
+ *
240
+ * Set by {@link AppStoreActions.showPointsSettings} to a new object on every call, so
241
+ * that a repeated request for the same points is a change too.
242
+ */
243
+ pointsSettingsRequest: {
244
+ pointsId: string;
245
+ } | null;
246
+ /**
247
+ * The latest request to show the settings of shapes, or `null` if there has
248
+ * been none
249
+ *
250
+ * Set by {@link AppStoreActions.showShapesSettings} to a new object on every call, so
251
+ * that a repeated request for the same shapes is a change too.
252
+ */
253
+ shapesSettingsRequest: {
254
+ shapesId: string;
255
+ } | null;
186
256
  };
187
257
 
188
258
  /**
@@ -384,7 +454,8 @@ export declare type ColorConfig = ConstantConfig<Color> | FromConfig<{
384
454
  palette: string;
385
455
  }> | GroupByConfig<false, {
386
456
  /**
387
- * ID of the color palette for mapping hashed group names to colors
457
+ * ID of the color palette whose colors are assigned to the groups in the
458
+ * order they first appear in the table column
388
459
  *
389
460
  * Only used when no project-global colormap is specified
390
461
  */
@@ -561,18 +632,22 @@ export declare class ConfigUtils {
561
632
  *
562
633
  * With a map, a group takes its value in the map, else the map's default,
563
634
  * else `defaultValue`; a map that does not exist gives every group
564
- * `defaultValue`. Without a map, a group takes the palette value its name
565
- * hashes to, or `defaultValue` if there is no palette.
635
+ * `defaultValue`. Without a map, the groups take the palette values in
636
+ * order, cycling through the palette if there are more groups than values;
637
+ * a group that is not listed, or every group if there is no palette, takes
638
+ * `defaultValue`.
566
639
  *
567
640
  * @param config - The group-by configuration
568
641
  * @param map - The map the configuration refers to (see
569
642
  * {@link findGroupByMap}), or `undefined` if it refers to none or the map
570
643
  * does not exist
571
644
  * @param defaultValue - The value of a group that nothing assigns one to
572
- * @param palette - The values to pick from by hash if there is no map
645
+ * @param palette - The values to assign to the groups if there is no map
646
+ * @param groups - All groups of the column the configuration groups by, in
647
+ * the order they take the palette values (see `TableUtils.loadGroupCounts`)
573
648
  * @returns The value of a group, by group name (the cell value as a string)
574
649
  */
575
- static createGroupValueGetter<TValue>(config: GroupByConfig<false>, map: GroupValueMap<TValue> | undefined, defaultValue: TValue, palette?: readonly TValue[]): (group: string) => TValue;
650
+ static createGroupValueGetter<TValue>(config: GroupByConfig<false>, map: GroupValueMap<TValue> | undefined, defaultValue: TValue, palette?: readonly TValue[], groups?: Iterable<string>): (group: string) => TValue;
576
651
  /**
577
652
  * Returns the table column a configuration groups by
578
653
  *
@@ -2243,9 +2318,12 @@ export declare type Path = Vertex[];
2243
2318
  * A plugin that can be registered with the application
2244
2319
  *
2245
2320
  * A plugin's lifecycle is `setup`, `mount`, `unmount`, `teardown`: registering
2246
- * the plugin calls {@link setup} and then {@link mount}, and unregistering it
2247
- * calls the callback returned by `mount` and then the callback returned by
2248
- * `setup`. All four steps are optional.
2321
+ * the plugin calls {@link setup}; mounting it, which happens when the user opens
2322
+ * it, calls {@link mount}; unmounting it, which happens when the user closes its
2323
+ * panel, calls the callback returned by `mount`; and unregistering it unmounts
2324
+ * it if it is mounted and then calls the callback returned by `setup`. A plugin
2325
+ * can be mounted and unmounted any number of times while it is registered. All
2326
+ * four steps are optional.
2249
2327
  */
2250
2328
  declare type Plugin_2 = {
2251
2329
  /** The unique ID of the plugin */
@@ -2255,7 +2333,7 @@ declare type Plugin_2 = {
2255
2333
  /**
2256
2334
  * Sets up the plugin and registers any necessary event listeners or other resources
2257
2335
  *
2258
- * Called first when the plugin is registered. A plugin whose `setup` throws is
2336
+ * Called once when the plugin is registered. A plugin whose `setup` throws is
2259
2337
  * not registered, and the teardown callback is not called: releasing whatever
2260
2338
  * `setup` had already acquired before it throws is `setup`'s own
2261
2339
  * responsibility.
@@ -2273,48 +2351,68 @@ declare type Plugin_2 = {
2273
2351
  /**
2274
2352
  * Mounts the plugin's user interface, if any
2275
2353
  *
2276
- * A plugin with a `mount` is shown as a panel titled with the plugin's
2277
- * {@link name}, for exactly as long as the plugin is registered. Closing the
2278
- * panel unregisters the plugin; a plugin without a `mount` can only be
2279
- * unregistered through the registry.
2354
+ * A plugin with a `mount` can be opened by the user, which mounts it and shows
2355
+ * it as a panel titled with the plugin's {@link name}. Closing the panel
2356
+ * unmounts the plugin again, but keeps it registered, so that it can be opened
2357
+ * again later.
2280
2358
  *
2281
- * Called once {@link setup} has returned successfully, with an empty element
2282
- * owned by the application. The element is not part of the document yet: the
2359
+ * Called each time the plugin is mounted, with a fresh, empty element owned by
2360
+ * the application. The element is not part of the document yet: the
2283
2361
  * application attaches it to the panel once that is shown, and keeps the user
2284
2362
  * interface mounted while the panel is hidden behind other tabs or moved.
2285
- * A plugin whose `mount` throws is not registered, and the teardown callback
2286
- * returned by `setup` is called.
2363
+ * A plugin whose `mount` throws is not mounted, but stays registered.
2287
2364
  *
2288
- * The container is discarded together with the plugin, so the returned
2365
+ * The container is discarded when the plugin is unmounted, so the returned
2289
2366
  * callback only has to release what is not plain DOM, such as store
2290
2367
  * subscriptions, listeners on `window`, or a React root.
2291
2368
  *
2292
2369
  * @param container - The element to mount the user interface into
2293
2370
  * @param stores - The stores provided by the application for the plugin to interact with
2294
2371
  * @returns An unmount callback that unmounts the user interface again, if
2295
- * any. It is called first when the plugin is unregistered, before the
2296
- * teardown callback returned by {@link setup}.
2372
+ * any. It is called when the plugin is unmounted, which also happens first
2373
+ * when a mounted plugin is unregistered, before the teardown callback
2374
+ * returned by {@link setup}.
2297
2375
  */
2298
2376
  mount?: (container: HTMLElement, stores: PluginStores) => (() => void) | void;
2299
2377
  };
2300
2378
  export { Plugin_2 as Plugin }
2301
2379
 
2302
2380
  /**
2303
- * The registry through which the application registers and unregisters plugins
2381
+ * The registry through which the application registers, mounts, unmounts and
2382
+ * unregisters plugins
2304
2383
  */
2305
2384
  export declare type PluginRegistry = {
2306
2385
  /**
2307
- * Registers a plugin, calling its `setup` and then its `mount`, if it has them
2386
+ * Registers a plugin, calling its `setup`, if it has one
2308
2387
  *
2309
- * Registering a plugin whose ID is already in use unregisters the previously
2310
- * registered plugin first.
2388
+ * The plugin is not mounted: that is up to the user, or to
2389
+ * {@link mountPlugin}. Registering a plugin whose ID is already in use
2390
+ * unregisters the previously registered plugin first.
2311
2391
  *
2312
2392
  * @param plugin - The plugin to register
2313
2393
  */
2314
2394
  registerPlugin: (plugin: Plugin_2) => void;
2315
2395
  /**
2316
- * Unregisters a registered plugin, calling the unmount callback returned by
2317
- * its `mount` and then the teardown callback returned by its `setup`
2396
+ * Mounts a registered plugin, calling its `mount` and showing its panel
2397
+ *
2398
+ * Does nothing if the plugin is not registered, has no `mount`, or is already
2399
+ * mounted.
2400
+ *
2401
+ * @param pluginId - The ID of the plugin to mount
2402
+ */
2403
+ mountPlugin: (pluginId: string) => void;
2404
+ /**
2405
+ * Unmounts a mounted plugin, removing its panel and calling the unmount
2406
+ * callback returned by its `mount`
2407
+ *
2408
+ * The plugin stays registered. Does nothing if the plugin is not mounted.
2409
+ *
2410
+ * @param pluginId - The ID of the plugin to unmount
2411
+ */
2412
+ unmountPlugin: (pluginId: string) => void;
2413
+ /**
2414
+ * Unregisters a registered plugin, unmounting it if it is mounted and then
2415
+ * calling the teardown callback returned by its `setup`
2318
2416
  *
2319
2417
  * @param pluginId - The ID of the plugin to unregister
2320
2418
  */
@@ -4199,24 +4297,32 @@ export declare interface TableData extends ItemsData {
4199
4297
  /**
4200
4298
  * Loads the number of rows per unique value of a column
4201
4299
  *
4300
+ * Only implemented by table data that can count the values more cheaply
4301
+ * than by loading the column (e.g. from file metadata); otherwise, callers
4302
+ * count the values loaded by {@link loadValues}.
4303
+ *
4202
4304
  * @typeParam T - Element type of the column
4203
4305
  * @param column - The column name
4204
4306
  * @param options - Optional abort signal and progress callback
4205
4307
  * @returns The row count of every unique column value, keyed by value, in the
4206
4308
  * order the values first appear
4207
4309
  */
4208
- loadUniqueValueCounts<T>(column: string, options?: {
4310
+ loadUniqueValueCounts?<T>(column: string, options?: {
4209
4311
  signal?: AbortSignal;
4210
4312
  onProgress?: ProgressCallback;
4211
4313
  }): Promise<Map<T, number>>;
4212
4314
  /**
4213
4315
  * Loads a column's minimum and maximum values
4214
4316
  *
4317
+ * Only implemented by table data that can determine the range more cheaply
4318
+ * than by loading the column (e.g. from file metadata); otherwise, callers
4319
+ * compute the range of the values loaded by {@link loadValues}.
4320
+ *
4215
4321
  * @param column - The column name
4216
4322
  * @param options - Optional abort signal and progress callback
4217
4323
  * @returns The numeric [min, max] value range of the column, or `undefined` if not numeric
4218
4324
  */
4219
- loadValueRange(column: string, options?: {
4325
+ loadValueRange?(column: string, options?: {
4220
4326
  signal?: AbortSignal;
4221
4327
  onProgress?: ProgressCallback;
4222
4328
  }): Promise<[number, number] | undefined>;
@@ -4274,7 +4380,8 @@ export declare const tableDefaults: {};
4274
4380
  * {@link fillFromTableGroups} fill a typed array with one packed value per
4275
4381
  * item from a table column, one by parsing the cell values and one by mapping
4276
4382
  * their distinct values as groups. IDs that the table does not contain fall
4277
- * back to a default value, with a warning.
4383
+ * back to a default value, with a warning. {@link loadGroupCounts} lists the
4384
+ * groups of a column, for all rows rather than per item.
4278
4385
  */
4279
4386
  export declare class TableUtils {
4280
4387
  /**
@@ -4332,6 +4439,67 @@ export declare class TableUtils {
4332
4439
  static fillFromTableGroups<TValue>(packedValues: TypedArray, tableData: TableData, ids: IDArray, column: string, defaultValue: TValue, mapGroupToValue: (group: string) => TValue, packValue: (value: TValue) => number, options?: {
4333
4440
  signal?: AbortSignal;
4334
4441
  }): Promise<void>;
4442
+ /**
4443
+ * Loads the range of a table column's numeric values
4444
+ *
4445
+ * Uses the table's own value range if it provides one, and otherwise
4446
+ * computes the range of the loaded column values (see
4447
+ * {@link computeValueRange}).
4448
+ *
4449
+ * @param tableData - The table to load the value range of
4450
+ * @param column - Name of the table column
4451
+ * @param options - Optional abort signal and progress callback
4452
+ * @returns The column's minimum and maximum value, or `undefined` if the
4453
+ * column is not numeric
4454
+ */
4455
+ static loadValueRange(tableData: TableData, column: string, options?: {
4456
+ signal?: AbortSignal;
4457
+ onProgress?: ProgressCallback;
4458
+ }): Promise<[number, number] | undefined>;
4459
+ /**
4460
+ * Computes the range of loaded table column values
4461
+ *
4462
+ * @param values - The column values
4463
+ * @param options - Optional abort signal
4464
+ * @returns The minimum and maximum value, or `undefined` if the values are
4465
+ * not numeric or hold no two distinct finite values
4466
+ */
4467
+ static computeValueRange(values: TypedArrayOrArray<unknown>, options?: {
4468
+ signal?: AbortSignal;
4469
+ }): Promise<[number, number] | undefined>;
4470
+ /**
4471
+ * Loads how many rows of a table each group of a column holds
4472
+ *
4473
+ * Groups are the cell values as strings, as {@link fillFromTableGroups} and
4474
+ * group-to-value maps key them, so values with the same string (e.g. `null`
4475
+ * and `"null"`) form one group.
4476
+ *
4477
+ * @param tableData - The table to load the groups of
4478
+ * @param column - Name of the table column to group by
4479
+ * @param options - Optional abort signal
4480
+ * @returns The row count of every group, in the order the groups first
4481
+ * appear in the column
4482
+ */
4483
+ static loadGroupCounts(tableData: TableData, column: string, options?: {
4484
+ signal?: AbortSignal;
4485
+ }): Promise<Map<string, number>>;
4486
+ /**
4487
+ * Loads the number of rows per unique value of a table column
4488
+ *
4489
+ * Uses the table's own counts if it provides them, and otherwise counts the
4490
+ * loaded column values (see `MathUtils.computeUniqueValueCounts`).
4491
+ *
4492
+ * @typeParam T - Element type of the column
4493
+ * @param tableData - The table to load the unique value counts of
4494
+ * @param column - Name of the table column
4495
+ * @param options - Optional abort signal and progress callback
4496
+ * @returns The row count of every unique column value, keyed by value, in the
4497
+ * order the values first appear
4498
+ */
4499
+ static loadUniqueValueCounts<T>(tableData: TableData, column: string, options?: {
4500
+ signal?: AbortSignal;
4501
+ onProgress?: ProgressCallback;
4502
+ }): Promise<Map<T, number>>;
4335
4503
  /**
4336
4504
  * Calls `callback` for every ID with the index of the table row holding that ID
4337
4505
  *
package/dist/index.js CHANGED
@@ -1697,26 +1697,32 @@ var ConfigUtils = class ConfigUtils {
1697
1697
  *
1698
1698
  * With a map, a group takes its value in the map, else the map's default,
1699
1699
  * else `defaultValue`; a map that does not exist gives every group
1700
- * `defaultValue`. Without a map, a group takes the palette value its name
1701
- * hashes to, or `defaultValue` if there is no palette.
1700
+ * `defaultValue`. Without a map, the groups take the palette values in
1701
+ * order, cycling through the palette if there are more groups than values;
1702
+ * a group that is not listed, or every group if there is no palette, takes
1703
+ * `defaultValue`.
1702
1704
  *
1703
1705
  * @param config - The group-by configuration
1704
1706
  * @param map - The map the configuration refers to (see
1705
1707
  * {@link findGroupByMap}), or `undefined` if it refers to none or the map
1706
1708
  * does not exist
1707
1709
  * @param defaultValue - The value of a group that nothing assigns one to
1708
- * @param palette - The values to pick from by hash if there is no map
1710
+ * @param palette - The values to assign to the groups if there is no map
1711
+ * @param groups - All groups of the column the configuration groups by, in
1712
+ * the order they take the palette values (see `TableUtils.loadGroupCounts`)
1709
1713
  * @returns The value of a group, by group name (the cell value as a string)
1710
1714
  */
1711
- static createGroupValueGetter(config, map, defaultValue, palette) {
1715
+ static createGroupValueGetter(config, map, defaultValue, palette, groups) {
1712
1716
  if (config.groupBy.map !== void 0) {
1713
1717
  if (map === void 0) return () => defaultValue;
1714
1718
  const values = new Map(Object.entries(map.values));
1715
1719
  const mapDefault = map.default ?? defaultValue;
1716
1720
  return (group) => values.get(group) ?? mapDefault;
1717
1721
  }
1718
- if (palette === void 0 || palette.length === 0) return () => defaultValue;
1719
- return (group) => palette[HashUtils.hash(group) % palette.length];
1722
+ if (palette === void 0 || palette.length === 0 || groups === void 0) return () => defaultValue;
1723
+ const paletteValues = /* @__PURE__ */ new Map();
1724
+ for (const group of groups) if (!paletteValues.has(group)) paletteValues.set(group, palette[paletteValues.size % palette.length]);
1725
+ return (group) => paletteValues.get(group) ?? defaultValue;
1720
1726
  }
1721
1727
  /**
1722
1728
  * Returns the table column a configuration groups by
@@ -2683,7 +2689,8 @@ var SourceUtils = class SourceUtils {
2683
2689
  * {@link fillFromTableGroups} fill a typed array with one packed value per
2684
2690
  * item from a table column, one by parsing the cell values and one by mapping
2685
2691
  * their distinct values as groups. IDs that the table does not contain fall
2686
- * back to a default value, with a warning.
2692
+ * back to a default value, with a warning. {@link loadGroupCounts} lists the
2693
+ * groups of a column, for all rows rather than per item.
2687
2694
  */
2688
2695
  var TableUtils = class TableUtils {
2689
2696
  /**
@@ -2777,6 +2784,81 @@ var TableUtils = class TableUtils {
2777
2784
  if (numMissingIds > 0) console.warn(`${numMissingIds} IDs missing in column ${column}, using default value`);
2778
2785
  }
2779
2786
  /**
2787
+ * Loads the range of a table column's numeric values
2788
+ *
2789
+ * Uses the table's own value range if it provides one, and otherwise
2790
+ * computes the range of the loaded column values (see
2791
+ * {@link computeValueRange}).
2792
+ *
2793
+ * @param tableData - The table to load the value range of
2794
+ * @param column - Name of the table column
2795
+ * @param options - Optional abort signal and progress callback
2796
+ * @returns The column's minimum and maximum value, or `undefined` if the
2797
+ * column is not numeric
2798
+ */
2799
+ static async loadValueRange(tableData, column, options) {
2800
+ if (tableData.loadValueRange !== void 0) return await tableData.loadValueRange(column, options);
2801
+ const values = await tableData.loadValues(column, options);
2802
+ return await TableUtils.computeValueRange(values, options);
2803
+ }
2804
+ /**
2805
+ * Computes the range of loaded table column values
2806
+ *
2807
+ * @param values - The column values
2808
+ * @param options - Optional abort signal
2809
+ * @returns The minimum and maximum value, or `undefined` if the values are
2810
+ * not numeric or hold no two distinct finite values
2811
+ */
2812
+ static async computeValueRange(values, options) {
2813
+ const { signal } = options ?? {};
2814
+ signal?.throwIfAborted();
2815
+ if (!ArrayBuffer.isView(values)) return;
2816
+ const [vmin, vmax] = await MathUtils.computeRange(values, { signal });
2817
+ return vmin < vmax ? [vmin, vmax] : void 0;
2818
+ }
2819
+ /**
2820
+ * Loads how many rows of a table each group of a column holds
2821
+ *
2822
+ * Groups are the cell values as strings, as {@link fillFromTableGroups} and
2823
+ * group-to-value maps key them, so values with the same string (e.g. `null`
2824
+ * and `"null"`) form one group.
2825
+ *
2826
+ * @param tableData - The table to load the groups of
2827
+ * @param column - Name of the table column to group by
2828
+ * @param options - Optional abort signal
2829
+ * @returns The row count of every group, in the order the groups first
2830
+ * appear in the column
2831
+ */
2832
+ static async loadGroupCounts(tableData, column, options) {
2833
+ const { signal } = options ?? {};
2834
+ signal?.throwIfAborted();
2835
+ const uniqueValueCounts = await TableUtils.loadUniqueValueCounts(tableData, column, { signal });
2836
+ const groupCounts = /* @__PURE__ */ new Map();
2837
+ for (const [value, count] of uniqueValueCounts) {
2838
+ const group = String(value);
2839
+ groupCounts.set(group, (groupCounts.get(group) ?? 0) + count);
2840
+ }
2841
+ return groupCounts;
2842
+ }
2843
+ /**
2844
+ * Loads the number of rows per unique value of a table column
2845
+ *
2846
+ * Uses the table's own counts if it provides them, and otherwise counts the
2847
+ * loaded column values (see `MathUtils.computeUniqueValueCounts`).
2848
+ *
2849
+ * @typeParam T - Element type of the column
2850
+ * @param tableData - The table to load the unique value counts of
2851
+ * @param column - Name of the table column
2852
+ * @param options - Optional abort signal and progress callback
2853
+ * @returns The row count of every unique column value, keyed by value, in the
2854
+ * order the values first appear
2855
+ */
2856
+ static async loadUniqueValueCounts(tableData, column, options) {
2857
+ if (tableData.loadUniqueValueCounts !== void 0) return await tableData.loadUniqueValueCounts(column, options);
2858
+ const values = await tableData.loadValues(column, options);
2859
+ return await MathUtils.computeUniqueValueCounts(values, options);
2860
+ }
2861
+ /**
2780
2862
  * Calls `callback` for every ID with the index of the table row holding that ID
2781
2863
  *
2782
2864
  * If `ids` is the table's own ID array, the ID at position `i` is held by
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tissuumaps/core",
3
- "version": "0.1.0-beta.0",
3
+ "version": "0.1.0-beta.2",
4
4
  "description": "Core model, types and utilities shared by the TissUUmaps packages",
5
5
  "homepage": "https://tissuumaps.github.io/TissUUmaps/",
6
6
  "bugs": "https://github.com/TissUUmaps/TissUUmaps/issues",
@@ -32,11 +32,13 @@
32
32
  "@jsonforms/core": "^3.8.0",
33
33
  "@types/d3-color": "^3.1.3",
34
34
  "@types/d3-scale-chromatic": "^3.1.0",
35
+ "immer": "^11.1.8",
35
36
  "openseadragon": "^6.1.1",
36
37
  "zustand": "^5.0.15"
37
38
  },
38
39
  "peerDependencies": {
39
40
  "@jsonforms/core": "^3.8.0",
41
+ "immer": "^11.1.8",
40
42
  "openseadragon": "^6.1.1",
41
43
  "zustand": "^5.0.15"
42
44
  },
@@ -44,6 +46,9 @@
44
46
  "@jsonforms/core": {
45
47
  "optional": true
46
48
  },
49
+ "immer": {
50
+ "optional": true
51
+ },
47
52
  "openseadragon": {
48
53
  "optional": true
49
54
  },