@vitessce/config 3.0.1 → 3.1.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.
@@ -1,3 +1,4 @@
1
+ /* eslint-disable react-hooks/rules-of-hooks */
1
2
  import { CoordinationType } from '@vitessce/constants-internal';
2
3
  import { fromEntries, getNextScope } from '@vitessce/utils';
3
4
 
@@ -101,6 +102,142 @@ export class VitessceConfigDataset {
101
102
  }
102
103
  }
103
104
 
105
+ function useComplexCoordinationHelper(scopes, coordinationScopes, coordinationScopesBy) {
106
+ // Set this.coordinationScopes and this.coordinationScopesBy by recursion on `scopes`.
107
+ /*
108
+ // Destructured, `scopes` might look like:
109
+ const {
110
+ [CoordinationType.SPATIAL_IMAGE_LAYER]: [
111
+ {
112
+ scope: imageLayerScope,
113
+ children: {
114
+ [CoordinationType.IMAGE]: { scope: imageScope },
115
+ [CoordinationType.SPATIAL_LAYER_VISIBLE]: { scope: imageVisibleScope },
116
+ [CoordinationType.SPATIAL_LAYER_OPACITY]: { scope: imageOpacityScope },
117
+ [CoordinationType.SPATIAL_IMAGE_CHANNEL]: [
118
+ {
119
+ scope: imageChannelScopeR,
120
+ children: {
121
+ [CoordinationType.SPATIAL_TARGET_C]: { scope: rTargetScope },
122
+ [CoordinationType.SPATIAL_CHANNEL_COLOR]: { scope: rColorScope },
123
+ },
124
+ },
125
+ {
126
+ scope: imageChannelScopeG,
127
+ children: {
128
+ [CoordinationType.SPATIAL_TARGET_C]: { scope: gTargetScope },
129
+ [CoordinationType.SPATIAL_CHANNEL_COLOR]: { scope: gColorScope },
130
+ },
131
+ },
132
+ ],
133
+ },
134
+ },
135
+ ],
136
+ // ...
137
+ } = scopes;
138
+
139
+ // This would set the values to:
140
+ this.coordinationScopes = {
141
+ // Add the top-level coordination types to `coordinationScopes`.
142
+ [CoordinationType.SPATIAL_IMAGE_LAYER]: [imageLayerScope.cScope],
143
+ };
144
+ this.coordinationScopesBy = {
145
+ [CoordinationType.SPATIAL_IMAGE_LAYER]: {
146
+ [CoordinationType.IMAGE]: {
147
+ [imageLayerScope.cScope]: imageScope.cScope,
148
+ },
149
+ [CoordinationType.SPATIAL_LAYER_VISIBLE]: {
150
+ [imageLayerScope.cScope]: imageVisibleScope.cScope,
151
+ },
152
+ [CoordinationType.SPATIAL_LAYER_OPACITY]: {
153
+ [imageLayerScope.cScope]: imageOpacityScope.cScope,
154
+ },
155
+ [CoordinationType.SPATIAL_IMAGE_CHANNEL]: {
156
+ [imageLayerScope.cScope]: [imageChannelScopeR.cScope, imageChannelScopeG.cScope],
157
+ },
158
+ },
159
+ [CoordinationType.SPATIAL_IMAGE_CHANNEL]: {
160
+ [CoordinationType.SPATIAL_TARGET_C]: {
161
+ [imageChannelScopeR.cScope]: rTargetScope.cScope,
162
+ [imageChannelScopeG.cScope]: gTargetScope.cScope,
163
+ },
164
+ [CoordinationType.SPATIAL_CHANNEL_COLOR]: {
165
+ [imageChannelScopeR.cScope]: rColorScope.cScope,
166
+ [imageChannelScopeG.cScope]: gColorScope.cScope,
167
+ },
168
+ },
169
+ };
170
+ */
171
+
172
+ // Recursive inner function.
173
+ function processLevel(parentType, parentScope, levelType, levelVal) {
174
+ if (Array.isArray(levelVal)) {
175
+ // eslint-disable-next-line no-param-reassign
176
+ coordinationScopesBy[parentType] = {
177
+ ...(coordinationScopesBy[parentType] || {}),
178
+ [levelType]: {
179
+ ...(coordinationScopesBy[parentType]?.[levelType] || {}),
180
+ [parentScope.cScope]: levelVal.map(childVal => childVal.scope.cScope),
181
+ },
182
+ };
183
+ levelVal.forEach((childVal) => {
184
+ if (childVal.children) {
185
+ // Continue recursion.
186
+ Object.entries(childVal.children)
187
+ .forEach(([nextLevelType, nextLevelVal]) => processLevel(
188
+ levelType, childVal.scope, nextLevelType, nextLevelVal,
189
+ ));
190
+ } // Else is the base case: no children
191
+ });
192
+ } else {
193
+ // eslint-disable-next-line no-param-reassign
194
+ coordinationScopesBy[parentType] = {
195
+ ...(coordinationScopesBy[parentType] || {}),
196
+ [levelType]: {
197
+ ...(coordinationScopesBy[parentType]?.[levelType] || {}),
198
+ [parentScope.cScope]: levelVal.scope.cScope,
199
+ },
200
+ };
201
+
202
+ if (levelVal.children) {
203
+ // Continue recursion.
204
+ Object.entries(levelVal.children)
205
+ .forEach(([nextLevelType, nextLevelVal]) => processLevel(
206
+ levelType, levelVal.scope, nextLevelType, nextLevelVal,
207
+ ));
208
+ } // Else is the base case: no children
209
+ }
210
+ }
211
+
212
+ Object.entries(scopes).forEach(([topLevelType, topLevelVal]) => {
213
+ if (Array.isArray(topLevelVal)) {
214
+ // eslint-disable-next-line no-param-reassign
215
+ coordinationScopes[topLevelType] = topLevelVal.map(levelVal => levelVal.scope.cScope);
216
+
217
+ topLevelVal.forEach((levelVal) => {
218
+ if (levelVal.children) {
219
+ // Begin recursion.
220
+ Object.entries(levelVal.children)
221
+ .forEach(([nextLevelType, nextLevelVal]) => processLevel(
222
+ topLevelType, levelVal.scope, nextLevelType, nextLevelVal,
223
+ ));
224
+ }
225
+ });
226
+ } else {
227
+ // eslint-disable-next-line no-param-reassign
228
+ coordinationScopes[topLevelType] = topLevelVal.scope.cScope;
229
+ if (topLevelVal.children) {
230
+ // Begin recursion.
231
+ Object.entries(topLevelVal.children)
232
+ .forEach(([nextLevelType, nextLevelVal]) => processLevel(
233
+ topLevelType, topLevelVal.scope, nextLevelType, nextLevelVal,
234
+ ));
235
+ }
236
+ }
237
+ });
238
+ return [coordinationScopes, coordinationScopesBy];
239
+ }
240
+
104
241
  /**
105
242
  * Class representing a view within a Vitessce layout.
106
243
  */
@@ -119,6 +256,7 @@ export class VitessceConfigView {
119
256
  this.view = {
120
257
  component,
121
258
  coordinationScopes,
259
+ coordinationScopesBy: undefined, // TODO: initialize from parameter?
122
260
  x,
123
261
  y,
124
262
  w,
@@ -140,6 +278,43 @@ export class VitessceConfigView {
140
278
  return this;
141
279
  }
142
280
 
281
+ useComplexCoordination(scopes) {
282
+ if (!this.view.coordinationScopes) {
283
+ this.view.coordinationScopes = {};
284
+ }
285
+ if (!this.view.coordinationScopesBy) {
286
+ this.view.coordinationScopesBy = {};
287
+ }
288
+ const [nextCoordinationScopes, nextCoordinationScopesBy] = useComplexCoordinationHelper(
289
+ scopes,
290
+ this.view.coordinationScopes,
291
+ this.view.coordinationScopesBy,
292
+ );
293
+ this.view.coordinationScopes = nextCoordinationScopes;
294
+ this.view.coordinationScopesBy = nextCoordinationScopesBy;
295
+ return this;
296
+ }
297
+
298
+ /**
299
+ * Attach meta coordination scopes to this view.
300
+ * @param {VitessceConfigMetaCoordinationScope} metaScope A meta coordination scope instance.
301
+ * @returns {VitessceConfigView} This, to allow chaining.
302
+ */
303
+ useMetaCoordination(metaScope) {
304
+ if (!this.view.coordinationScopes) {
305
+ this.view.coordinationScopes = {};
306
+ }
307
+ this.view.coordinationScopes[CoordinationType.META_COORDINATION_SCOPES] = [
308
+ ...(this.view.coordinationScopes[CoordinationType.META_COORDINATION_SCOPES] || []),
309
+ metaScope.metaScope.cScope,
310
+ ];
311
+ this.view.coordinationScopes[CoordinationType.META_COORDINATION_SCOPES_BY] = [
312
+ ...(this.view.coordinationScopes[CoordinationType.META_COORDINATION_SCOPES_BY] || []),
313
+ metaScope.metaByScope.cScope,
314
+ ];
315
+ return this;
316
+ }
317
+
143
318
  /**
144
319
  * Set the x, y, w, h values for this view.
145
320
  * @param {number} x The x-coordinate of the view in the layout.
@@ -217,6 +392,17 @@ export function vconcat(...views) {
217
392
  return vcvvc;
218
393
  }
219
394
 
395
+ // would import as CL for convenience
396
+ class CoordinationLevel {
397
+ constructor(value) {
398
+ this.value = value;
399
+ }
400
+ }
401
+
402
+ export function CL(value) {
403
+ return new CoordinationLevel(value);
404
+ }
405
+
220
406
  /**
221
407
  * Class representing a coordination scope in the coordination space.
222
408
  */
@@ -243,6 +429,72 @@ export class VitessceConfigCoordinationScope {
243
429
  }
244
430
  }
245
431
 
432
+ /**
433
+ * Class representing a pair of coordination scopes,
434
+ * for metaCoordinationScopes and metaCoordinationScopesBy,
435
+ * respectively, in the coordination space.
436
+ */
437
+ export class VitessceConfigMetaCoordinationScope {
438
+ /**
439
+ * Construct a new coordination scope instance.
440
+ * @param {string} metaScope The name of the coordination scope for metaCoordinationScopes.
441
+ * @param {string} metaByScope The name of the coordination scope for metaCoordinationScopesBy.
442
+ */
443
+ constructor(metaScope, metaByScope) {
444
+ this.metaScope = new VitessceConfigCoordinationScope(
445
+ CoordinationType.META_COORDINATION_SCOPES,
446
+ metaScope,
447
+ );
448
+ this.metaByScope = new VitessceConfigCoordinationScope(
449
+ CoordinationType.META_COORDINATION_SCOPES_BY,
450
+ metaByScope,
451
+ );
452
+ }
453
+
454
+ /**
455
+ * Attach coordination scopes to this meta scope.
456
+ * @param {...VitessceConfigCoordinationScope} args A variable number of
457
+ * coordination scope instances.
458
+ * @returns {VitessceConfigMetaCoordinationScope} This, to allow chaining.
459
+ */
460
+ useCoordination(...args) {
461
+ const cScopes = args;
462
+ const metaScopesVal = this.metaScope.cValue;
463
+ cScopes.forEach((cScope) => {
464
+ metaScopesVal[cScope.cType] = cScope.cScope;
465
+ });
466
+ this.metaScope.setValue(metaScopesVal);
467
+ return this;
468
+ }
469
+
470
+ useComplexCoordination(scopes) {
471
+ if (!this.metaScope.cValue) {
472
+ this.metaScope.setValue({});
473
+ }
474
+ if (!this.metaByScope.cValue) {
475
+ this.metaByScope.setValue({});
476
+ }
477
+ const [metaScopesVal, metaByScopesVal] = useComplexCoordinationHelper(
478
+ scopes,
479
+ this.metaScope.cValue,
480
+ this.metaByScope.cValue,
481
+ );
482
+ this.metaScope.setValue(metaScopesVal);
483
+ this.metaByScope.setValue(metaByScopesVal);
484
+ return this;
485
+ }
486
+
487
+ /**
488
+ * Set the coordination value of the coordination scope.
489
+ * @param {any} cValue The value to set.
490
+ * @returns {VitessceConfigCoordinationScope} This, to allow chaining.
491
+ */
492
+ setValue(cValue) {
493
+ this.cValue = cValue;
494
+ return this;
495
+ }
496
+ }
497
+
246
498
  /**
247
499
  * Class representing a Vitessce view config.
248
500
  */
@@ -383,6 +635,147 @@ export class VitessceConfig {
383
635
  return result;
384
636
  }
385
637
 
638
+ addMetaCoordination() {
639
+ const prevMetaScopes = (
640
+ this.config.coordinationSpace[CoordinationType.META_COORDINATION_SCOPES]
641
+ ? Object.keys(this.config.coordinationSpace[CoordinationType.META_COORDINATION_SCOPES])
642
+ : []
643
+ );
644
+ const prevMetaByScopes = (
645
+ this.config.coordinationSpace[CoordinationType.META_COORDINATION_SCOPES_BY]
646
+ ? Object.keys(this.config.coordinationSpace[CoordinationType.META_COORDINATION_SCOPES_BY])
647
+ : []
648
+ );
649
+ const metaContainer = new VitessceConfigMetaCoordinationScope(
650
+ getNextScope(prevMetaScopes),
651
+ getNextScope(prevMetaByScopes),
652
+ );
653
+ if (!this.config.coordinationSpace[CoordinationType.META_COORDINATION_SCOPES]) {
654
+ this.config.coordinationSpace[CoordinationType.META_COORDINATION_SCOPES] = {};
655
+ }
656
+ if (!this.config.coordinationSpace[CoordinationType.META_COORDINATION_SCOPES_BY]) {
657
+ this.config.coordinationSpace[CoordinationType.META_COORDINATION_SCOPES_BY] = {};
658
+ }
659
+ // eslint-disable-next-line max-len
660
+ this.config.coordinationSpace[CoordinationType.META_COORDINATION_SCOPES][metaContainer.metaScope.cScope] = metaContainer.metaScope;
661
+ // eslint-disable-next-line max-len
662
+ this.config.coordinationSpace[CoordinationType.META_COORDINATION_SCOPES_BY][metaContainer.metaByScope.cScope] = metaContainer.metaByScope;
663
+ return metaContainer;
664
+ }
665
+
666
+ addComplexCoordination(input) {
667
+ /*
668
+ // The value for `input` might look like:
669
+ {
670
+ [CoordinationType.SPATIAL_IMAGE_LAYER]: CL([
671
+ {
672
+ [CoordinationType.IMAGE]: 'S-1905-017737_bf',
673
+ [CoordinationType.SPATIAL_LAYER_VISIBLE]: true,
674
+ [CoordinationType.SPATIAL_LAYER_OPACITY]: 1,
675
+ [CoordinationType.SPATIAL_IMAGE_CHANNEL]: CL([
676
+ {
677
+ [CoordinationType.SPATIAL_TARGET_C]: 0,
678
+ [CoordinationType.SPATIAL_CHANNEL_COLOR]: [255, 0, 0],
679
+ },
680
+ {
681
+ [CoordinationType.SPATIAL_TARGET_C]: 1,
682
+ [CoordinationType.SPATIAL_CHANNEL_COLOR]: [0, 255, 0],
683
+ },
684
+ ]),
685
+ },
686
+ ]),
687
+ [CoordinationType.SPATIAL_SEGMENTATION_LAYER]: CL([
688
+ {
689
+ [CoordinationType.IMAGE]: 'S-1905-017737',
690
+ [CoordinationType.SPATIAL_LAYER_VISIBLE]: true,
691
+ [CoordinationType.SPATIAL_LAYER_OPACITY]: 1,
692
+ [CoordinationType.SPATIAL_SEGMENTATION_CHANNEL]: CL([
693
+ {
694
+ [CoordinationType.OBS_TYPE]: 'Cortical Interstitia',
695
+ [CoordinationType.SPATIAL_TARGET_C]: 0,
696
+ [CoordinationType.SPATIAL_CHANNEL_COLOR]: [255, 0, 0],
697
+ },
698
+ {
699
+ [CoordinationType.OBS_TYPE]: 'Non-Globally Sclerotic Glomeruli',
700
+ [CoordinationType.SPATIAL_TARGET_C]: 1,
701
+ [CoordinationType.SPATIAL_CHANNEL_COLOR]: [255, 0, 0],
702
+ },
703
+ {
704
+ [CoordinationType.OBS_TYPE]: 'Globally Sclerotic Glomeruli',
705
+ [CoordinationType.SPATIAL_TARGET_C]: 2,
706
+ [CoordinationType.SPATIAL_CHANNEL_COLOR]: [255, 0, 0],
707
+ },
708
+ ]),
709
+ },
710
+ ]),
711
+ }
712
+ // Which would correspond to this `output`,
713
+ // a valid input for `VitessceConfigMetaCoordinationScope.useComplexCoordination()`:
714
+ {
715
+ [CoordinationType.SPATIAL_IMAGE_LAYER]: [
716
+ {
717
+ scope: imageLayerScope,
718
+ children: {
719
+ [CoordinationType.IMAGE]: { scope: imageScope },
720
+ [CoordinationType.SPATIAL_LAYER_VISIBLE]: { scope: imageVisibleScope },
721
+ [CoordinationType.SPATIAL_LAYER_OPACITY]: { scope: imageOpacityScope },
722
+ [CoordinationType.SPATIAL_IMAGE_CHANNEL]: [
723
+ {
724
+ scope: imageChannelScopeR,
725
+ children: {
726
+ [CoordinationType.SPATIAL_TARGET_C]: { scope: rTargetScope },
727
+ [CoordinationType.SPATIAL_CHANNEL_COLOR]: { scope: rColorScope },
728
+ },
729
+ },
730
+ {
731
+ scope: imageChannelScopeG,
732
+ children: {
733
+ [CoordinationType.SPATIAL_TARGET_C]: { scope: gTargetScope },
734
+ [CoordinationType.SPATIAL_CHANNEL_COLOR]: { scope: gColorScope },
735
+ },
736
+ },
737
+ ],
738
+ },
739
+ },
740
+ ],
741
+ // ...
742
+ }
743
+ */
744
+ const processLevel = (level) => {
745
+ const result = {};
746
+ Object.entries(level).forEach(([cType, nextLevelOrInitialValue]) => {
747
+ // Check if value of object is instanceof CoordinationLevel
748
+ // (otherwise assume it is the coordination value).
749
+ if (nextLevelOrInitialValue instanceof CoordinationLevel) {
750
+ const nextLevel = nextLevelOrInitialValue.value;
751
+ if (Array.isArray(nextLevel)) {
752
+ result[cType] = nextLevel.map((nextEl) => {
753
+ const [dummyScope] = this.addCoordination(cType);
754
+ // TODO: set a better initial value for dummy cases.
755
+ dummyScope.setValue('__dummy__');
756
+ return {
757
+ scope: dummyScope,
758
+ children: processLevel(nextEl),
759
+ };
760
+ });
761
+ } else {
762
+ throw new Error('Expected CoordinationLevel.value to be an array.');
763
+ }
764
+ } else {
765
+ // Base case.
766
+ const initialValue = nextLevelOrInitialValue;
767
+ const [scope] = this.addCoordination(cType);
768
+ scope.setValue(initialValue);
769
+ result[cType] = { scope };
770
+ }
771
+ });
772
+ return result;
773
+ };
774
+ // Begin recursion.
775
+ const output = processLevel(input);
776
+ return output;
777
+ }
778
+
386
779
  /**
387
780
  * A convenience function for setting up new coordination scopes across a set of views.
388
781
  * @param {VitessceConfigView[]} views An array of view objects to link together.