react-msaview 9.1.1 → 9.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.
Files changed (89) hide show
  1. package/bundle/index.js +79 -79
  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 +31 -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/settings/TrackList.js +8 -5
  23. package/dist/components/settings/TrackList.js.map +1 -1
  24. package/dist/components/tracks/TrackTooltipContent.js +2 -1
  25. package/dist/components/tracks/TrackTooltipContent.js.map +1 -1
  26. package/dist/components/tracks/drawTracks.js +13 -6
  27. package/dist/components/tracks/drawTracks.js.map +1 -1
  28. package/dist/components/tree/TreeCanvas.js +8 -6
  29. package/dist/components/tree/TreeCanvas.js.map +1 -1
  30. package/dist/constants.d.ts +1 -0
  31. package/dist/constants.js +2 -0
  32. package/dist/constants.js.map +1 -1
  33. package/dist/element.d.ts +11 -7
  34. package/dist/element.js +46 -8
  35. package/dist/element.js.map +1 -1
  36. package/dist/featurePanels.d.ts +0 -7
  37. package/dist/featurePanels.js +1 -16
  38. package/dist/featurePanels.js.map +1 -1
  39. package/dist/ggplotPalettes.js +12 -1
  40. package/dist/ggplotPalettes.js.map +1 -1
  41. package/dist/groupKeys.d.ts +6 -0
  42. package/dist/groupKeys.js +52 -0
  43. package/dist/groupKeys.js.map +1 -0
  44. package/dist/index.d.ts +1 -1
  45. package/dist/model.d.ts +40 -17
  46. package/dist/model.js +123 -54
  47. package/dist/model.js.map +1 -1
  48. package/dist/scales.d.ts +114 -13
  49. package/dist/scales.js +154 -19
  50. package/dist/scales.js.map +1 -1
  51. package/dist/svgTestUtil.d.ts +22 -12
  52. package/dist/types.d.ts +30 -28
  53. package/dist/useDragScroll.d.ts +4 -3
  54. package/dist/useDragScroll.js +9 -6
  55. package/dist/useDragScroll.js.map +1 -1
  56. package/dist/useWheelScroll.d.ts +26 -8
  57. package/dist/useWheelScroll.js +108 -71
  58. package/dist/useWheelScroll.js.map +1 -1
  59. package/dist/version.d.ts +1 -1
  60. package/dist/version.js +1 -1
  61. package/dist/viewerSnapshot.js +2 -1
  62. package/dist/viewerSnapshot.js.map +1 -1
  63. package/package.json +3 -3
  64. package/src/components/DragHandle.tsx +2 -1
  65. package/src/components/MSAViewer.tsx +11 -8
  66. package/src/components/Track.tsx +2 -8
  67. package/src/components/VerticalScrollbar.tsx +1 -1
  68. package/src/components/header/Header.tsx +2 -1
  69. package/src/components/minimap/Minimap.tsx +1 -1
  70. package/src/components/msa/MSACanvas.tsx +14 -35
  71. package/src/components/rowpanels/RowPanels.tsx +8 -10
  72. package/src/components/settings/SettingsPanel.tsx +2 -1
  73. package/src/components/settings/TrackList.tsx +9 -4
  74. package/src/components/tracks/TrackTooltipContent.tsx +2 -1
  75. package/src/components/tracks/drawTracks.ts +15 -7
  76. package/src/components/tree/TreeCanvas.tsx +8 -10
  77. package/src/constants.ts +2 -0
  78. package/src/element.ts +53 -8
  79. package/src/featurePanels.ts +1 -25
  80. package/src/ggplotPalettes.ts +13 -1
  81. package/src/groupKeys.ts +54 -0
  82. package/src/index.ts +13 -2
  83. package/src/model.ts +174 -72
  84. package/src/scales.ts +268 -25
  85. package/src/types.ts +29 -26
  86. package/src/useDragScroll.ts +11 -8
  87. package/src/useWheelScroll.ts +124 -84
  88. package/src/version.ts +1 -1
  89. package/src/viewerSnapshot.ts +3 -1
package/src/model.ts CHANGED
@@ -60,6 +60,7 @@ import {
60
60
  minRowHeight,
61
61
  minTreeWidth,
62
62
  pxPerTreeDepth,
63
+ resizeHandleWidth,
63
64
  rowPanelHeaderHeight,
64
65
  rowTintAlpha,
65
66
  scrollZoomAxes,
@@ -70,13 +71,12 @@ import {
70
71
  } from './constants.ts'
71
72
  import { createPaletteMap } from './createPaletteMap.ts'
72
73
  import { exportFileName } from './exportFileName.ts'
73
- import { featureName } from './featureFields.ts'
74
+ import { featureField, featureName } from './featureFields.ts'
74
75
  import {
75
76
  colorByRow,
76
77
  featureColorMap,
77
78
  featureLabelMap,
78
79
  featurePanelSpans,
79
- resolveFeatureScale,
80
80
  rowPanelWidth,
81
81
  stringFields,
82
82
  } from './featurePanels.ts'
@@ -102,7 +102,14 @@ import {
102
102
  visibleColToGlobalCol,
103
103
  visibleColsBefore,
104
104
  } from './rowCoordinateCalculations.ts'
105
- import { resolveScale } from './scales.ts'
105
+ import {
106
+ colorTable,
107
+ declarationKey,
108
+ readColumnTrack,
109
+ readEncoding,
110
+ readRowPanel,
111
+ resolveShared,
112
+ } from './scales.ts'
106
113
  import { buildSeqPosIndex } from './seqPosToGlobalCol.ts'
107
114
  import { maxBitsFor } from './sequenceLogo.ts'
108
115
  import {
@@ -136,6 +143,15 @@ import type { ColumnStats } from './columnStats.ts'
136
143
  import type { ScrollZoomAxis } from './constants.ts'
137
144
  import type { HierarchyNode } from './hierarchy.ts'
138
145
  import type { ExportSvgOptions } from './renderToSvg.tsx'
146
+ import type {
147
+ ColorChannel,
148
+ LegacyColumnTrack,
149
+ LegacyEncoding,
150
+ LegacyRowPanel,
151
+ ScaleTable,
152
+ ScaleUse,
153
+ SharedScale,
154
+ } from './scales.ts'
139
155
  import type {
140
156
  Annotation,
141
157
  Arc,
@@ -600,7 +616,11 @@ function stateModelFactory({
600
616
  * per-column string drawn as a text track. See docs/layers.md
601
617
  */
602
618
  columnTracks: stripDefault(
603
- types.array(types.frozen<ColumnTrackSpec>()),
619
+ types.array(
620
+ types.snapshotProcessor(types.frozen<ColumnTrackSpec>(), {
621
+ preProcessor: readColumnTrack,
622
+ }),
623
+ ),
604
624
  [],
605
625
  ),
606
626
 
@@ -688,20 +708,35 @@ function stateModelFactory({
688
708
  clades: stripDefault(types.array(types.frozen<Clade>()), []),
689
709
  /**
690
710
  * #property
691
- * what the viewer's own marks read from `rowData`:
692
- * `{channel, field, scale?}` per channel, where `channel` is
693
- * `tipLabel` or `rowTint`. See docs/layers.md
711
+ * what the viewer's own marks read from `rowData` or the feature
712
+ * table: `{channel, field, range?, domain?, title?}` per channel. The
713
+ * earlier `scale: {palette}` and `scale: {map}` read as `range` and
714
+ * `domain`. See docs/layers.md
694
715
  */
695
- encodings: stripDefault(types.array(types.frozen<Encoding>()), []),
716
+ encodings: stripDefault(
717
+ types.array(
718
+ types.snapshotProcessor(types.frozen<Encoding>(), {
719
+ preProcessor: readEncoding,
720
+ }),
721
+ ),
722
+ [],
723
+ ),
696
724
  /**
697
725
  * #property
698
726
  * panels drawn between the tree and the alignment:
699
- * `{kind: "strip", field, scale?, width?, header?}` colors one cell
700
- * per row from a `rowData` field, and
727
+ * `{kind: "strip", field, range?, domain?, title?, width?, header?}`
728
+ * colors one cell per row from a `rowData` field, and
701
729
  * `{kind: "features", x, encoding?, transform?, width?, header?}`
702
730
  * draws the spans the GFF carries. See docs/layers.md
703
731
  */
704
- rowPanels: stripDefault(types.array(types.frozen<RowPanelSpec>()), []),
732
+ rowPanels: stripDefault(
733
+ types.array(
734
+ types.snapshotProcessor(types.frozen<RowPanelSpec>(), {
735
+ preProcessor: readRowPanel,
736
+ }),
737
+ ),
738
+ [],
739
+ ),
705
740
  }),
706
741
  )
707
742
  .volatile(() => ({
@@ -765,9 +800,10 @@ function stateModelFactory({
765
800
  volatileWidth: undefined as number | undefined,
766
801
  /**
767
802
  * #volatile
768
- * resize handle width between tree and msa area, px
803
+ * thickness of the dividers, between the tree and the alignment and under
804
+ * the view, px
769
805
  */
770
- resizeHandleWidth: 5,
806
+ resizeHandleWidth,
771
807
 
772
808
  /**
773
809
  * #volatile
@@ -2310,7 +2346,7 @@ function stateModelFactory({
2310
2346
  return [track.id, { arcs }] as const
2311
2347
  }
2312
2348
  if (track.kind === 'bar') {
2313
- const max = track.max ?? 1
2349
+ const max = track.scales?.y?.domainMax ?? 1
2314
2350
  const values = skip(project(track, track.values ?? [], 0)).map(
2315
2351
  v => Math.min(1, Math.max(0, v / max)),
2316
2352
  )
@@ -2361,7 +2397,12 @@ function stateModelFactory({
2361
2397
  height: this.trackHeight(track.kind, heightKey, track.height),
2362
2398
  barColor: track.color,
2363
2399
  arcColor: track.color,
2364
- customColorScheme: track.colors,
2400
+ customColorScheme: track.encoding?.color
2401
+ ? colorTable(track.encoding.color, [
2402
+ ...(track.encoding.color.domain ?? []),
2403
+ ...new Set(track.data),
2404
+ ])
2405
+ : undefined,
2365
2406
  data: content?.data,
2366
2407
  arcs: content?.arcs,
2367
2408
  },
@@ -2498,6 +2539,30 @@ function stateModelFactory({
2498
2539
  }))
2499
2540
  },
2500
2541
 
2542
+ /**
2543
+ * #getter
2544
+ * the reference row (`relativeTo`) repeated above the alignment, so it
2545
+ * stays in view while the rows compared against it scroll
2546
+ */
2547
+ get referenceTrackModels(): BasicTrack[] {
2548
+ const { relativeTo } = self
2549
+ const data =
2550
+ relativeTo === undefined ? undefined : self.columns.get(relativeTo)
2551
+ return relativeTo === undefined || data === undefined
2552
+ ? []
2553
+ : [
2554
+ {
2555
+ model: {
2556
+ id: 'reference-row',
2557
+ name: relativeTo,
2558
+ kind: 'reference' as const,
2559
+ data,
2560
+ height: self.rowHeight,
2561
+ },
2562
+ },
2563
+ ]
2564
+ },
2565
+
2501
2566
  get tracks(): BasicTrack[] {
2502
2567
  return [
2503
2568
  ...this.adapterTrackModels,
@@ -2506,6 +2571,7 @@ function stateModelFactory({
2506
2571
  // every computed track reads the alignment's columns, and a tree, a
2507
2572
  // GFF and a features panel draw a figure with none
2508
2573
  ...(self.numColumns > 0 ? this.computedTrackModels : []),
2574
+ ...this.referenceTrackModels,
2509
2575
  ]
2510
2576
  },
2511
2577
 
@@ -3393,8 +3459,8 @@ function stateModelFactory({
3393
3459
  /**
3394
3460
  * #action
3395
3461
  */
3396
- setColumnTracks(tracks: ColumnTrackSpec[]) {
3397
- self.columnTracks.replace(tracks)
3462
+ setColumnTracks(tracks: (ColumnTrackSpec | LegacyColumnTrack)[]) {
3463
+ self.columnTracks.replace(tracks.map(readColumnTrack))
3398
3464
  },
3399
3465
  /**
3400
3466
  * #action
@@ -3408,15 +3474,15 @@ function stateModelFactory({
3408
3474
  * #action
3409
3475
  * replace what the viewer's marks read from the row table
3410
3476
  */
3411
- setEncodings(encodings: Encoding[]) {
3412
- self.encodings.replace(encodings)
3477
+ setEncodings(encodings: (Encoding | LegacyEncoding)[]) {
3478
+ self.encodings.replace(encodings.map(readEncoding))
3413
3479
  },
3414
3480
  /**
3415
3481
  * #action
3416
3482
  * replace the panels drawn between the tree and the alignment
3417
3483
  */
3418
- setRowPanels(panels: RowPanelSpec[]) {
3419
- self.rowPanels.replace(panels)
3484
+ setRowPanels(panels: (RowPanelSpec | LegacyRowPanel)[]) {
3485
+ self.rowPanels.replace(panels.map(readRowPanel))
3420
3486
  },
3421
3487
  /**
3422
3488
  * #action
@@ -3618,42 +3684,41 @@ function stateModelFactory({
3618
3684
  * the categorical color keys drawn for this view, shared by the on-screen
3619
3685
  * legend overlay and the SVG export's reserved column. The domain overlay
3620
3686
  * produces the first, listing the `featureFill` scale where an encoding
3621
- * names one. Every field a row-table encoding or a row panel reads
3622
- * produces one more, so two channels over one field, or two strips over
3623
- * it, list that field once. A strip's `legend` names the title it lists
3624
- * under, so strips over eight fields with one set of colors list one
3625
- * legend between them
3687
+ * names one. Every scale a row-table encoding or a row panel reads
3688
+ * produces one more, keyed by its declaration, so two channels or two
3689
+ * strips declaring one scale list it once, and strips over eight fields
3690
+ * naming one `title` list one legend between them
3626
3691
  */
3627
3692
  get legends(): Legend[] {
3628
3693
  const { featureFillEncoding, fillPalette, visibleDomainTypes } = this
3629
3694
  const byKey = new Map<string, Legend>()
3695
+ const ids = new Set<string>()
3630
3696
  // the accession palette keys on no field, so it takes a key of its own
3631
3697
  const add = (
3632
- field: string | undefined,
3698
+ key: string | undefined,
3699
+ title: string | undefined,
3633
3700
  entries: LegendEntry[],
3634
- id?: string,
3701
+ overlay = false,
3635
3702
  ) => {
3636
- if (entries.length === 0) {
3703
+ const k = key ?? 'domains'
3704
+ if (entries.length === 0 || byKey.has(k)) {
3637
3705
  return
3638
3706
  }
3639
- const key = field === undefined ? 'domains' : `field:${field}`
3640
- const legend = byKey.get(key)
3641
- if (legend) {
3642
- const seen = new Set(legend.entries.map(e => e.id))
3643
- legend.entries.push(...entries.filter(e => !seen.has(e.id)))
3644
- } else {
3645
- byKey.set(key, {
3646
- id: id ?? (field === undefined ? 'domains' : `rowData-${field}`),
3647
- title: field ?? 'Domains',
3648
- entries: [...entries],
3649
- })
3707
+ const base =
3708
+ overlay || key === undefined ? 'domains' : `rowData-${title}`
3709
+ let id = base
3710
+ for (let n = 2; ids.has(id); n++) {
3711
+ id = `${base}-${n}`
3650
3712
  }
3713
+ ids.add(id)
3714
+ byKey.set(k, { id, title: title ?? 'Domains', entries })
3651
3715
  }
3652
3716
  // the overlay draws the domain key; with no columns it draws nothing,
3653
3717
  // and a features panel taking the same colors lists them instead
3654
3718
  if (self.actuallyShowDomains && self.numColumns > 0) {
3655
3719
  add(
3656
- featureFillEncoding?.field,
3720
+ featureFillEncoding?.key,
3721
+ featureFillEncoding?.title,
3657
3722
  featureFillEncoding
3658
3723
  ? featureFillEncoding.legend
3659
3724
  : visibleDomainTypes.map(d => ({
@@ -3661,16 +3726,16 @@ function stateModelFactory({
3661
3726
  label: d.name,
3662
3727
  color: d.color ?? fillPalette[d.accession]!,
3663
3728
  })),
3664
- 'domains',
3729
+ true,
3665
3730
  )
3666
3731
  }
3667
- for (const { channel, field, legend } of this.resolvedEncodings) {
3732
+ for (const { channel, key, title, legend } of this.resolvedEncodings) {
3668
3733
  if (!featureChannels.has(channel)) {
3669
- add(field, legend)
3734
+ add(key, title, legend)
3670
3735
  }
3671
3736
  }
3672
- for (const { legendTitle, legend } of this.rowPanelScales) {
3673
- add(legendTitle, legend)
3737
+ for (const { legendKey, legendTitle, legend } of this.rowPanelScales) {
3738
+ add(legendKey, legendTitle, legend)
3674
3739
  }
3675
3740
  return [...byKey.values()]
3676
3741
  },
@@ -3976,23 +4041,68 @@ function stateModelFactory({
3976
4041
  * table or the encodings, never per row per frame.
3977
4042
  */
3978
4043
  get resolvedEncodings(): ResolvedEncoding[] {
3979
- const rows = Object.values(self.rowData)
3980
4044
  return self.encodings.map(encoding => ({
3981
4045
  ...encoding,
3982
- ...(featureChannels.has(encoding.channel)
3983
- ? resolveFeatureScale(
3984
- encoding.field,
3985
- encoding.scale,
3986
- self.allAnnotations,
3987
- self.filteredAnnotations,
3988
- )
3989
- : resolveScale(
3990
- encoding.scale,
3991
- rows.map(row => row?.[encoding.field]).filter(notEmpty),
3992
- )),
4046
+ ...this.scaleOf(
4047
+ encoding,
4048
+ featureChannels.has(encoding.channel) ? 'features' : 'rows',
4049
+ ),
3993
4050
  }))
3994
4051
  },
3995
4052
 
4053
+ /**
4054
+ * #getter
4055
+ * every color object the encodings and the row panels declare, keyed
4056
+ * by declaration (`declarationKey`) and resolved once over the union
4057
+ * of the values its records' fields take, so a value two strips share
4058
+ * takes one color in both. A feature field reads every feature loaded,
4059
+ * so hiding a type in the filter dialog leaves the rest in their
4060
+ * colors, and its legend lists the values of the drawn features.
4061
+ */
4062
+ get resolvedScales() {
4063
+ const rows = Object.values(self.rowData)
4064
+ const featureValues = (list: Annotation[], field: string) =>
4065
+ list.map(a => featureField(a, field)).filter(notEmpty)
4066
+ const use = (channel: ColorChannel, table: ScaleTable): ScaleUse => ({
4067
+ spec: channel,
4068
+ field: channel.field,
4069
+ table,
4070
+ ...(table === 'rows'
4071
+ ? {
4072
+ values: rows.map(row => row?.[channel.field]).filter(notEmpty),
4073
+ }
4074
+ : {
4075
+ values: featureValues(self.allAnnotations, channel.field),
4076
+ shown: featureValues(self.filteredAnnotations, channel.field),
4077
+ }),
4078
+ })
4079
+ return resolveShared([
4080
+ ...self.encodings.map(encoding =>
4081
+ use(
4082
+ encoding,
4083
+ featureChannels.has(encoding.channel) ? 'features' : 'rows',
4084
+ ),
4085
+ ),
4086
+ ...self.rowPanels.flatMap(panel =>
4087
+ panel.kind === 'strip'
4088
+ ? [use(panel, 'rows')]
4089
+ : panel.encoding?.color
4090
+ ? [use(panel.encoding.color, 'features')]
4091
+ : [],
4092
+ ),
4093
+ ])
4094
+ },
4095
+
4096
+ /**
4097
+ * #method
4098
+ * the shared scale a record's color object resolves to
4099
+ */
4100
+ scaleOf(channel: ColorChannel, table: ScaleTable): SharedScale {
4101
+ return this.resolvedScales.get(
4102
+ declarationKey({ spec: channel, field: channel.field, table }),
4103
+ )!
4104
+ },
4105
+
3996
4106
  /**
3997
4107
  * #getter
3998
4108
  * each row panel's scale resolved against the values its field takes,
@@ -4007,12 +4117,7 @@ function stateModelFactory({
4007
4117
  if (panel.kind === 'features') {
4008
4118
  const color = panel.encoding?.color
4009
4119
  const encoding = color
4010
- ? resolveFeatureScale(
4011
- color.field,
4012
- color.scale,
4013
- self.allAnnotations,
4014
- annotations,
4015
- )
4120
+ ? { field: color.field, ...this.scaleOf(color, 'features') }
4016
4121
  : featureFillEncoding
4017
4122
  const label = panel.encoding?.label
4018
4123
  return {
@@ -4020,7 +4125,8 @@ function stateModelFactory({
4020
4125
  x: panel.x,
4021
4126
  header: panel.header ?? '',
4022
4127
  field: encoding?.field,
4023
- legendTitle: encoding?.field,
4128
+ legendKey: encoding?.key,
4129
+ legendTitle: encoding?.title,
4024
4130
  colors: color
4025
4131
  ? featureColorMap(annotations, encoding, fillPalette)
4026
4132
  : this.featureColors,
@@ -4036,17 +4142,13 @@ function stateModelFactory({
4036
4142
  })),
4037
4143
  }
4038
4144
  }
4039
- const { colorOf, legend } = resolveScale(
4040
- panel.scale,
4041
- Object.values(rowData)
4042
- .map(row => row?.[panel.field])
4043
- .filter(notEmpty),
4044
- )
4145
+ const { colorOf, legend, key, title } = this.scaleOf(panel, 'rows')
4045
4146
  return {
4046
4147
  kind: panel.kind,
4047
4148
  field: panel.field,
4048
4149
  header: panel.header ?? panel.field,
4049
- legendTitle: panel.legend ?? panel.field,
4150
+ legendKey: key,
4151
+ legendTitle: title,
4050
4152
  colors: colorByRow(rowData, panel.field, colorOf),
4051
4153
  legend,
4052
4154
  }