react-msaview 9.2.0 → 9.4.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.
Files changed (80) hide show
  1. package/bundle/index.js +74 -74
  2. package/bundle/index.js.map +4 -4
  3. package/dist/components/DragHandle.js +2 -1
  4. package/dist/components/DragHandle.js.map +1 -1
  5. package/dist/components/MSAViewer.d.ts +29 -19
  6. package/dist/components/MSAViewer.js +2 -1
  7. package/dist/components/MSAViewer.js.map +1 -1
  8. package/dist/components/Track.js +2 -5
  9. package/dist/components/Track.js.map +1 -1
  10. package/dist/components/VerticalScrollbar.js +1 -1
  11. package/dist/components/VerticalScrollbar.js.map +1 -1
  12. package/dist/components/header/Header.js +2 -1
  13. package/dist/components/header/Header.js.map +1 -1
  14. package/dist/components/minimap/Minimap.js +1 -1
  15. package/dist/components/minimap/Minimap.js.map +1 -1
  16. package/dist/components/msa/MSACanvas.js +16 -27
  17. package/dist/components/msa/MSACanvas.js.map +1 -1
  18. package/dist/components/rowpanels/RowPanels.js +8 -6
  19. package/dist/components/rowpanels/RowPanels.js.map +1 -1
  20. package/dist/components/settings/SettingsPanel.js +2 -1
  21. package/dist/components/settings/SettingsPanel.js.map +1 -1
  22. package/dist/components/tree/TreeCanvas.js +8 -6
  23. package/dist/components/tree/TreeCanvas.js.map +1 -1
  24. package/dist/constants.d.ts +1 -0
  25. package/dist/constants.js +2 -0
  26. package/dist/constants.js.map +1 -1
  27. package/dist/element.d.ts +11 -7
  28. package/dist/element.js +46 -8
  29. package/dist/element.js.map +1 -1
  30. package/dist/featurePanels.d.ts +0 -7
  31. package/dist/featurePanels.js +1 -16
  32. package/dist/featurePanels.js.map +1 -1
  33. package/dist/ggplotPalettes.js +12 -1
  34. package/dist/ggplotPalettes.js.map +1 -1
  35. package/dist/groupKeys.d.ts +6 -0
  36. package/dist/groupKeys.js +52 -0
  37. package/dist/groupKeys.js.map +1 -0
  38. package/dist/index.d.ts +1 -1
  39. package/dist/model.d.ts +38 -18
  40. package/dist/model.js +107 -56
  41. package/dist/model.js.map +1 -1
  42. package/dist/scales.d.ts +114 -13
  43. package/dist/scales.js +154 -19
  44. package/dist/scales.js.map +1 -1
  45. package/dist/svgTestUtil.d.ts +20 -12
  46. package/dist/types.d.ts +29 -27
  47. package/dist/useDragScroll.d.ts +4 -3
  48. package/dist/useDragScroll.js +9 -6
  49. package/dist/useDragScroll.js.map +1 -1
  50. package/dist/useWheelScroll.d.ts +26 -8
  51. package/dist/useWheelScroll.js +108 -71
  52. package/dist/useWheelScroll.js.map +1 -1
  53. package/dist/version.d.ts +1 -1
  54. package/dist/version.js +1 -1
  55. package/dist/viewerSnapshot.js +2 -1
  56. package/dist/viewerSnapshot.js.map +1 -1
  57. package/package.json +3 -3
  58. package/src/components/DragHandle.tsx +2 -1
  59. package/src/components/MSAViewer.tsx +11 -8
  60. package/src/components/Track.tsx +2 -8
  61. package/src/components/VerticalScrollbar.tsx +1 -1
  62. package/src/components/header/Header.tsx +2 -1
  63. package/src/components/minimap/Minimap.tsx +1 -1
  64. package/src/components/msa/MSACanvas.tsx +14 -35
  65. package/src/components/rowpanels/RowPanels.tsx +8 -10
  66. package/src/components/settings/SettingsPanel.tsx +2 -1
  67. package/src/components/tree/TreeCanvas.tsx +8 -10
  68. package/src/constants.ts +2 -0
  69. package/src/element.ts +53 -8
  70. package/src/featurePanels.ts +1 -25
  71. package/src/ggplotPalettes.ts +13 -1
  72. package/src/groupKeys.ts +54 -0
  73. package/src/index.ts +13 -2
  74. package/src/model.ts +159 -74
  75. package/src/scales.ts +268 -25
  76. package/src/types.ts +28 -25
  77. package/src/useDragScroll.ts +11 -8
  78. package/src/useWheelScroll.ts +124 -84
  79. package/src/version.ts +1 -1
  80. package/src/viewerSnapshot.ts +3 -1
package/dist/model.js CHANGED
@@ -13,11 +13,11 @@ import { packDomainLanes } from './components/msa/packDomainLanes.js';
13
13
  import { visibleColRange } from './components/msa/visibleColRange.js';
14
14
  import { cladeGutterWidth } from './components/tree/cladeBrackets.js';
15
15
  import { scaleBarLength } from './components/tree/scaleBar.js';
16
- import { defaultAllowedGappyness, defaultColWidth, defaultColorSchemeName, defaultCurrentAlignment, defaultDrawMsaLetters, defaultHeight, defaultHideGaps, defaultRowHeight, defaultScrollX, defaultScrollY, defaultScrollZoom, defaultScrollZoomAxis, defaultShowDomainLegend, defaultShowDomains, defaultSubFeatureRows, fontSizeFor, labelFitFontSize, maxCellSize, maxFitTreeWidth, maxTreeAreaShare, maxInlineSnapshotBytes as defaultMaxInlineSnapshotBytes, maxNeighborJoiningRows, minColWidth, minLetterColWidth, minLetterRowHeight, minRowHeight, minTreeWidth, pxPerTreeDepth, rowPanelHeaderHeight, rowTintAlpha, scrollZoomAxes, segmentFeatureTypes, segmentShades, settingsPanelWidth, treeScaleBarHeight, } from './constants.js';
16
+ import { defaultAllowedGappyness, defaultColWidth, defaultColorSchemeName, defaultCurrentAlignment, defaultDrawMsaLetters, defaultHeight, defaultHideGaps, defaultRowHeight, defaultScrollX, defaultScrollY, defaultScrollZoom, defaultScrollZoomAxis, defaultShowDomainLegend, defaultShowDomains, defaultSubFeatureRows, fontSizeFor, labelFitFontSize, maxCellSize, maxFitTreeWidth, maxTreeAreaShare, maxInlineSnapshotBytes as defaultMaxInlineSnapshotBytes, maxNeighborJoiningRows, minColWidth, minLetterColWidth, minLetterRowHeight, minRowHeight, minTreeWidth, pxPerTreeDepth, resizeHandleWidth, rowPanelHeaderHeight, rowTintAlpha, scrollZoomAxes, segmentFeatureTypes, segmentShades, settingsPanelWidth, treeScaleBarHeight, } from './constants.js';
17
17
  import { createPaletteMap } from './createPaletteMap.js';
18
18
  import { exportFileName } from './exportFileName.js';
19
- import { featureName } from './featureFields.js';
20
- import { colorByRow, featureColorMap, featureLabelMap, featurePanelSpans, resolveFeatureScale, rowPanelWidth, stringFields, } from './featurePanels.js';
19
+ import { featureField, featureName } from './featureFields.js';
20
+ import { colorByRow, featureColorMap, featureLabelMap, featurePanelSpans, rowPanelWidth, stringFields, } from './featurePanels.js';
21
21
  import { featuresToAnnotations } from './featuresLayer.js';
22
22
  import { installFilehandleLoader } from './filehandleLoader.js';
23
23
  import { find, hierarchy, layoutTree, leaves, nodeCoveringRows, } from './hierarchy.js';
@@ -29,7 +29,7 @@ import { calculateNeighborJoiningTree } from './neighborJoining.js';
29
29
  import { calculatePropertyConservation } from './propertyConservation.js';
30
30
  import { preorder } from './rerootTree.js';
31
31
  import { globalColToVisibleCol, seqPosOfGlobalCol, visibleColToGlobalCol, visibleColsBefore, } from './rowCoordinateCalculations.js';
32
- import { resolveScale } from './scales.js';
32
+ import { colorTable, declarationKey, readColumnTrack, readEncoding, readRowPanel, resolveShared, } from './scales.js';
33
33
  import { buildSeqPosIndex } from './seqPosToGlobalCol.js';
34
34
  import { maxBitsFor } from './sequenceLogo.js';
35
35
  import { blosum62ColumnColors, identityColumnColors, } from './similarityColors.js';
@@ -391,7 +391,9 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
391
391
  * tracks supplied as data: per-column values drawn as bars, or a
392
392
  * per-column string drawn as a text track. See docs/layers.md
393
393
  */
394
- columnTracks: stripDefault(types.array(types.frozen()), []),
394
+ columnTracks: stripDefault(types.array(types.snapshotProcessor(types.frozen(), {
395
+ preProcessor: readColumnTrack,
396
+ })), []),
395
397
  /**
396
398
  * #property
397
399
  * row-to-structure residue correspondence, computed outside the viewer
@@ -471,20 +473,25 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
471
473
  clades: stripDefault(types.array(types.frozen()), []),
472
474
  /**
473
475
  * #property
474
- * what the viewer's own marks read from `rowData`:
475
- * `{channel, field, scale?}` per channel, where `channel` is
476
- * `tipLabel` or `rowTint`. See docs/layers.md
476
+ * what the viewer's own marks read from `rowData` or the feature
477
+ * table: `{channel, field, range?, domain?, title?}` per channel. The
478
+ * earlier `scale: {palette}` and `scale: {map}` read as `range` and
479
+ * `domain`. See docs/layers.md
477
480
  */
478
- encodings: stripDefault(types.array(types.frozen()), []),
481
+ encodings: stripDefault(types.array(types.snapshotProcessor(types.frozen(), {
482
+ preProcessor: readEncoding,
483
+ })), []),
479
484
  /**
480
485
  * #property
481
486
  * panels drawn between the tree and the alignment:
482
- * `{kind: "strip", field, scale?, width?, header?}` colors one cell
483
- * per row from a `rowData` field, and
487
+ * `{kind: "strip", field, range?, domain?, title?, width?, header?}`
488
+ * colors one cell per row from a `rowData` field, and
484
489
  * `{kind: "features", x, encoding?, transform?, width?, header?}`
485
490
  * draws the spans the GFF carries. See docs/layers.md
486
491
  */
487
- rowPanels: stripDefault(types.array(types.frozen()), []),
492
+ rowPanels: stripDefault(types.array(types.snapshotProcessor(types.frozen(), {
493
+ preProcessor: readRowPanel,
494
+ })), []),
488
495
  }))
489
496
  .volatile(() => ({
490
497
  /**
@@ -545,9 +552,10 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
545
552
  volatileWidth: undefined,
546
553
  /**
547
554
  * #volatile
548
- * resize handle width between tree and msa area, px
555
+ * thickness of the dividers, between the tree and the alignment and under
556
+ * the view, px
549
557
  */
550
- resizeHandleWidth: 5,
558
+ resizeHandleWidth,
551
559
  /**
552
560
  * #volatile
553
561
  * size of blocks of content to be drawn, px
@@ -1943,7 +1951,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
1943
1951
  return [track.id, { arcs }];
1944
1952
  }
1945
1953
  if (track.kind === 'bar') {
1946
- const max = track.max ?? 1;
1954
+ const max = track.scales?.y?.domainMax ?? 1;
1947
1955
  const values = skip(project(track, track.values ?? [], 0)).map(v => Math.min(1, Math.max(0, v / max)));
1948
1956
  return [track.id, { values }];
1949
1957
  }
@@ -1987,7 +1995,12 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
1987
1995
  height: this.trackHeight(track.kind, heightKey, track.height),
1988
1996
  barColor: track.color,
1989
1997
  arcColor: track.color,
1990
- customColorScheme: track.colors,
1998
+ customColorScheme: track.encoding?.color
1999
+ ? colorTable(track.encoding.color, [
2000
+ ...(track.encoding.color.domain ?? []),
2001
+ ...new Set(track.data),
2002
+ ])
2003
+ : undefined,
1991
2004
  data: content?.data,
1992
2005
  arcs: content?.arcs,
1993
2006
  },
@@ -2911,7 +2924,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
2911
2924
  * #action
2912
2925
  */
2913
2926
  setColumnTracks(tracks) {
2914
- self.columnTracks.replace(tracks);
2927
+ self.columnTracks.replace(tracks.map(readColumnTrack));
2915
2928
  },
2916
2929
  /**
2917
2930
  * #action
@@ -2926,14 +2939,14 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
2926
2939
  * replace what the viewer's marks read from the row table
2927
2940
  */
2928
2941
  setEncodings(encodings) {
2929
- self.encodings.replace(encodings);
2942
+ self.encodings.replace(encodings.map(readEncoding));
2930
2943
  },
2931
2944
  /**
2932
2945
  * #action
2933
2946
  * replace the panels drawn between the tree and the alignment
2934
2947
  */
2935
2948
  setRowPanels(panels) {
2936
- self.rowPanels.replace(panels);
2949
+ self.rowPanels.replace(panels.map(readRowPanel));
2937
2950
  },
2938
2951
  /**
2939
2952
  * #action
@@ -3046,10 +3059,15 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
3046
3059
  /**
3047
3060
  * #getter
3048
3061
  * categorical feature types (InterPro domains and the like) that each get
3049
- * their own color and a legend entry
3062
+ * their own color and a legend entry, in palette order: grouped by
3063
+ * feature type, types in the order they first appear. A GenPept
3064
+ * record's CDD Sites follow every Region, so the palette spends the
3065
+ * colors after the domains' on the sites inside them
3050
3066
  */
3051
3067
  get categoricalDomainTypes() {
3052
- return [...self.annotationTypes.values()].filter(d => !segmentFeatureTypes.has(d.featureType ?? ''));
3068
+ const types = [...self.annotationTypes.values()].filter(d => !segmentFeatureTypes.has(d.featureType ?? ''));
3069
+ const typeOrder = [...new Set(types.map(d => d.featureType))];
3070
+ return types.toSorted((a, b) => typeOrder.indexOf(a.featureType) - typeOrder.indexOf(b.featureType));
3053
3071
  },
3054
3072
  get fillPalette() {
3055
3073
  const segments = Object.fromEntries(this.segmentDomainTypes.map((d, i) => [
@@ -3116,52 +3134,47 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
3116
3134
  * the categorical color keys drawn for this view, shared by the on-screen
3117
3135
  * legend overlay and the SVG export's reserved column. The domain overlay
3118
3136
  * produces the first, listing the `featureFill` scale where an encoding
3119
- * names one. Every field a row-table encoding or a row panel reads
3120
- * produces one more, so two channels over one field, or two strips over
3121
- * it, list that field once. A strip's `legend` names the title it lists
3122
- * under, so strips over eight fields with one set of colors list one
3123
- * legend between them
3137
+ * names one. Every scale a row-table encoding or a row panel reads
3138
+ * produces one more, keyed by its declaration, so two channels or two
3139
+ * strips declaring one scale list it once, and strips over eight fields
3140
+ * naming one `title` list one legend between them
3124
3141
  */
3125
3142
  get legends() {
3126
3143
  const { featureFillEncoding, fillPalette, visibleDomainTypes } = this;
3127
3144
  const byKey = new Map();
3145
+ const ids = new Set();
3128
3146
  // the accession palette keys on no field, so it takes a key of its own
3129
- const add = (field, entries, id) => {
3130
- if (entries.length === 0) {
3147
+ const add = (key, title, entries, overlay = false) => {
3148
+ const k = key ?? 'domains';
3149
+ if (entries.length === 0 || byKey.has(k)) {
3131
3150
  return;
3132
3151
  }
3133
- const key = field === undefined ? 'domains' : `field:${field}`;
3134
- const legend = byKey.get(key);
3135
- if (legend) {
3136
- const seen = new Set(legend.entries.map(e => e.id));
3137
- legend.entries.push(...entries.filter(e => !seen.has(e.id)));
3138
- }
3139
- else {
3140
- byKey.set(key, {
3141
- id: id ?? (field === undefined ? 'domains' : `rowData-${field}`),
3142
- title: field ?? 'Domains',
3143
- entries: [...entries],
3144
- });
3152
+ const base = overlay || key === undefined ? 'domains' : `rowData-${title}`;
3153
+ let id = base;
3154
+ for (let n = 2; ids.has(id); n++) {
3155
+ id = `${base}-${n}`;
3145
3156
  }
3157
+ ids.add(id);
3158
+ byKey.set(k, { id, title: title ?? 'Domains', entries });
3146
3159
  };
3147
3160
  // the overlay draws the domain key; with no columns it draws nothing,
3148
3161
  // and a features panel taking the same colors lists them instead
3149
3162
  if (self.actuallyShowDomains && self.numColumns > 0) {
3150
- add(featureFillEncoding?.field, featureFillEncoding
3163
+ add(featureFillEncoding?.key, featureFillEncoding?.title, featureFillEncoding
3151
3164
  ? featureFillEncoding.legend
3152
3165
  : visibleDomainTypes.map(d => ({
3153
3166
  id: d.accession,
3154
3167
  label: d.name,
3155
3168
  color: d.color ?? fillPalette[d.accession],
3156
- })), 'domains');
3169
+ })), true);
3157
3170
  }
3158
- for (const { channel, field, legend } of this.resolvedEncodings) {
3171
+ for (const { channel, key, title, legend } of this.resolvedEncodings) {
3159
3172
  if (!featureChannels.has(channel)) {
3160
- add(field, legend);
3173
+ add(key, title, legend);
3161
3174
  }
3162
3175
  }
3163
- for (const { legendTitle, legend } of this.rowPanelScales) {
3164
- add(legendTitle, legend);
3176
+ for (const { legendKey, legendTitle, legend } of this.rowPanelScales) {
3177
+ add(legendKey, legendTitle, legend);
3165
3178
  }
3166
3179
  return [...byKey.values()];
3167
3180
  },
@@ -3435,14 +3448,52 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
3435
3448
  * table or the encodings, never per row per frame.
3436
3449
  */
3437
3450
  get resolvedEncodings() {
3438
- const rows = Object.values(self.rowData);
3439
3451
  return self.encodings.map(encoding => ({
3440
3452
  ...encoding,
3441
- ...(featureChannels.has(encoding.channel)
3442
- ? resolveFeatureScale(encoding.field, encoding.scale, self.allAnnotations, self.filteredAnnotations)
3443
- : resolveScale(encoding.scale, rows.map(row => row?.[encoding.field]).filter(notEmpty))),
3453
+ ...this.scaleOf(encoding, featureChannels.has(encoding.channel) ? 'features' : 'rows'),
3444
3454
  }));
3445
3455
  },
3456
+ /**
3457
+ * #getter
3458
+ * every color object the encodings and the row panels declare, keyed
3459
+ * by declaration (`declarationKey`) and resolved once over the union
3460
+ * of the values its records' fields take, so a value two strips share
3461
+ * takes one color in both. A feature field reads every feature loaded,
3462
+ * so hiding a type in the filter dialog leaves the rest in their
3463
+ * colors, and its legend lists the values of the drawn features.
3464
+ */
3465
+ get resolvedScales() {
3466
+ const rows = Object.values(self.rowData);
3467
+ const featureValues = (list, field) => list.map(a => featureField(a, field)).filter(notEmpty);
3468
+ const use = (channel, table) => ({
3469
+ spec: channel,
3470
+ field: channel.field,
3471
+ table,
3472
+ ...(table === 'rows'
3473
+ ? {
3474
+ values: rows.map(row => row?.[channel.field]).filter(notEmpty),
3475
+ }
3476
+ : {
3477
+ values: featureValues(self.allAnnotations, channel.field),
3478
+ shown: featureValues(self.filteredAnnotations, channel.field),
3479
+ }),
3480
+ });
3481
+ return resolveShared([
3482
+ ...self.encodings.map(encoding => use(encoding, featureChannels.has(encoding.channel) ? 'features' : 'rows')),
3483
+ ...self.rowPanels.flatMap(panel => panel.kind === 'strip'
3484
+ ? [use(panel, 'rows')]
3485
+ : panel.encoding?.color
3486
+ ? [use(panel.encoding.color, 'features')]
3487
+ : []),
3488
+ ]);
3489
+ },
3490
+ /**
3491
+ * #method
3492
+ * the shared scale a record's color object resolves to
3493
+ */
3494
+ scaleOf(channel, table) {
3495
+ return this.resolvedScales.get(declarationKey({ spec: channel, field: channel.field, table }));
3496
+ },
3446
3497
  /**
3447
3498
  * #getter
3448
3499
  * each row panel's scale resolved against the values its field takes,
@@ -3457,7 +3508,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
3457
3508
  if (panel.kind === 'features') {
3458
3509
  const color = panel.encoding?.color;
3459
3510
  const encoding = color
3460
- ? resolveFeatureScale(color.field, color.scale, self.allAnnotations, annotations)
3511
+ ? { field: color.field, ...this.scaleOf(color, 'features') }
3461
3512
  : featureFillEncoding;
3462
3513
  const label = panel.encoding?.label;
3463
3514
  return {
@@ -3465,7 +3516,8 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
3465
3516
  x: panel.x,
3466
3517
  header: panel.header ?? '',
3467
3518
  field: encoding?.field,
3468
- legendTitle: encoding?.field,
3519
+ legendKey: encoding?.key,
3520
+ legendTitle: encoding?.title,
3469
3521
  colors: color
3470
3522
  ? featureColorMap(annotations, encoding, fillPalette)
3471
3523
  : this.featureColors,
@@ -3480,14 +3532,13 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
3480
3532
  })),
3481
3533
  };
3482
3534
  }
3483
- const { colorOf, legend } = resolveScale(panel.scale, Object.values(rowData)
3484
- .map(row => row?.[panel.field])
3485
- .filter(notEmpty));
3535
+ const { colorOf, legend, key, title } = this.scaleOf(panel, 'rows');
3486
3536
  return {
3487
3537
  kind: panel.kind,
3488
3538
  field: panel.field,
3489
3539
  header: panel.header ?? panel.field,
3490
- legendTitle: panel.legend ?? panel.field,
3540
+ legendKey: key,
3541
+ legendTitle: title,
3491
3542
  colors: colorByRow(rowData, panel.field, colorOf),
3492
3543
  legend,
3493
3544
  };