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/dist/model.d.ts CHANGED
@@ -2,6 +2,7 @@ import type { ColumnStats } from './columnStats.ts';
2
2
  import type { ScrollZoomAxis } from './constants.ts';
3
3
  import type { HierarchyNode } from './hierarchy.ts';
4
4
  import type { ExportSvgOptions } from './renderToSvg.tsx';
5
+ import type { ColorChannel, LegacyColumnTrack, LegacyEncoding, LegacyRowPanel, ScaleTable, SharedScale } from './scales.ts';
5
6
  import type { Annotation, Arc, BasicTrack, Cell, Clade, ResidueMappingProblem, ColumnTrackSpec, DomainBand, Encoding, Feature, Highlight, Legend, MsaSelection, NodeWithIds, Region, ResidueMapping, ResolvedClade, ResolvedEncoding, ResolvedFeaturePanel, ResolvedStripPanel, ResolvedHighlight, ResolvedSelection, TreeRoot, ResolvedRowPanel, RowDataInput, RowPanelSpec, RowResidue, StructureResidue, TrackKind, UnshareableData, Viewport } from './types.ts';
6
7
  import type { ZoomSnapshot } from './zoomSnapshot.ts';
7
8
  import type { FileLocation as FileLocationType } from '@jbrowse/core/util/types';
@@ -542,7 +543,7 @@ declare function stateModelFactory({ maxInlineSnapshotBytes, }?: {
542
543
  * tracks supplied as data: per-column values drawn as bars, or a
543
544
  * per-column string drawn as a text track. See docs/layers.md
544
545
  */
545
- columnTracks: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IType<ColumnTrackSpec, ColumnTrackSpec, ColumnTrackSpec>>, [undefined]>;
546
+ columnTracks: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").ISnapshotProcessor<import("@jbrowse/mobx-state-tree").IType<ColumnTrackSpec, ColumnTrackSpec, ColumnTrackSpec>, ColumnTrackSpec, import("@jbrowse/mobx-state-tree")._NotCustomized>>, [undefined]>;
546
547
  /**
547
548
  * #property
548
549
  * row-to-structure residue correspondence, computed outside the viewer
@@ -634,20 +635,21 @@ declare function stateModelFactory({ maxInlineSnapshotBytes, }?: {
634
635
  clades: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IType<Clade, Clade, Clade>>, [undefined]>;
635
636
  /**
636
637
  * #property
637
- * what the viewer's own marks read from `rowData`:
638
- * `{channel, field, scale?}` per channel, where `channel` is
639
- * `tipLabel` or `rowTint`. See docs/layers.md
638
+ * what the viewer's own marks read from `rowData` or the feature
639
+ * table: `{channel, field, range?, domain?, title?}` per channel. The
640
+ * earlier `scale: {palette}` and `scale: {map}` read as `range` and
641
+ * `domain`. See docs/layers.md
640
642
  */
641
- encodings: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IType<Encoding, Encoding, Encoding>>, [undefined]>;
643
+ encodings: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").ISnapshotProcessor<import("@jbrowse/mobx-state-tree").IType<Encoding, Encoding, Encoding>, Encoding | LegacyEncoding, import("@jbrowse/mobx-state-tree")._NotCustomized>>, [undefined]>;
642
644
  /**
643
645
  * #property
644
646
  * panels drawn between the tree and the alignment:
645
- * `{kind: "strip", field, scale?, width?, header?}` colors one cell
646
- * per row from a `rowData` field, and
647
+ * `{kind: "strip", field, range?, domain?, title?, width?, header?}`
648
+ * colors one cell per row from a `rowData` field, and
647
649
  * `{kind: "features", x, encoding?, transform?, width?, header?}`
648
650
  * draws the spans the GFF carries. See docs/layers.md
649
651
  */
650
- rowPanels: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IType<RowPanelSpec, RowPanelSpec, RowPanelSpec>>, [undefined]>;
652
+ rowPanels: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").ISnapshotProcessor<import("@jbrowse/mobx-state-tree").IType<RowPanelSpec, RowPanelSpec, RowPanelSpec>, LegacyRowPanel | RowPanelSpec, import("@jbrowse/mobx-state-tree")._NotCustomized>>, [undefined]>;
651
653
  }, {
652
654
  queueOfDialogs: [import("@jbrowse/core/util").DialogComponentType, any][];
653
655
  } & {
@@ -739,7 +741,8 @@ declare function stateModelFactory({ maxInlineSnapshotBytes, }?: {
739
741
  volatileWidth: number | undefined;
740
742
  /**
741
743
  * #volatile
742
- * resize handle width between tree and msa area, px
744
+ * thickness of the dividers, between the tree and the alignment and under
745
+ * the view, px
743
746
  */
744
747
  resizeHandleWidth: number;
745
748
  /**
@@ -1594,6 +1597,12 @@ declare function stateModelFactory({ maxInlineSnapshotBytes, }?: {
1594
1597
  * and the alphabet, not on zoom
1595
1598
  */
1596
1599
  readonly computedTrackModels: BasicTrack[];
1600
+ /**
1601
+ * #getter
1602
+ * the reference row (`relativeTo`) repeated above the alignment, so it
1603
+ * stays in view while the rows compared against it scroll
1604
+ */
1605
+ readonly referenceTrackModels: BasicTrack[];
1597
1606
  readonly tracks: BasicTrack[];
1598
1607
  /**
1599
1608
  * #getter
@@ -1992,7 +2001,7 @@ declare function stateModelFactory({ maxInlineSnapshotBytes, }?: {
1992
2001
  /**
1993
2002
  * #action
1994
2003
  */
1995
- setColumnTracks(tracks: ColumnTrackSpec[]): void;
2004
+ setColumnTracks(tracks: (ColumnTrackSpec | LegacyColumnTrack)[]): void;
1996
2005
  /**
1997
2006
  * #action
1998
2007
  * replace the row table, which the model keeps as the JSON string
@@ -2003,12 +2012,12 @@ declare function stateModelFactory({ maxInlineSnapshotBytes, }?: {
2003
2012
  * #action
2004
2013
  * replace what the viewer's marks read from the row table
2005
2014
  */
2006
- setEncodings(encodings: Encoding[]): void;
2015
+ setEncodings(encodings: (Encoding | LegacyEncoding)[]): void;
2007
2016
  /**
2008
2017
  * #action
2009
2018
  * replace the panels drawn between the tree and the alignment
2010
2019
  */
2011
- setRowPanels(panels: RowPanelSpec[]): void;
2020
+ setRowPanels(panels: (RowPanelSpec | LegacyRowPanel)[]): void;
2012
2021
  /**
2013
2022
  * #action
2014
2023
  * replace the alignment<->structure correspondence (see docs/layers.md)
@@ -2115,11 +2124,10 @@ declare function stateModelFactory({ maxInlineSnapshotBytes, }?: {
2115
2124
  * the categorical color keys drawn for this view, shared by the on-screen
2116
2125
  * legend overlay and the SVG export's reserved column. The domain overlay
2117
2126
  * produces the first, listing the `featureFill` scale where an encoding
2118
- * names one. Every field a row-table encoding or a row panel reads
2119
- * produces one more, so two channels over one field, or two strips over
2120
- * it, list that field once. A strip's `legend` names the title it lists
2121
- * under, so strips over eight fields with one set of colors list one
2122
- * legend between them
2127
+ * names one. Every scale a row-table encoding or a row panel reads
2128
+ * produces one more, keyed by its declaration, so two channels or two
2129
+ * strips declaring one scale list it once, and strips over eight fields
2130
+ * naming one `title` list one legend between them
2123
2131
  */
2124
2132
  readonly legends: Legend[];
2125
2133
  /**
@@ -2250,6 +2258,21 @@ declare function stateModelFactory({ maxInlineSnapshotBytes, }?: {
2250
2258
  * table or the encodings, never per row per frame.
2251
2259
  */
2252
2260
  readonly resolvedEncodings: ResolvedEncoding[];
2261
+ /**
2262
+ * #getter
2263
+ * every color object the encodings and the row panels declare, keyed
2264
+ * by declaration (`declarationKey`) and resolved once over the union
2265
+ * of the values its records' fields take, so a value two strips share
2266
+ * takes one color in both. A feature field reads every feature loaded,
2267
+ * so hiding a type in the filter dialog leaves the rest in their
2268
+ * colors, and its legend lists the values of the drawn features.
2269
+ */
2270
+ readonly resolvedScales: Map<string, SharedScale>;
2271
+ /**
2272
+ * #method
2273
+ * the shared scale a record's color object resolves to
2274
+ */
2275
+ scaleOf(channel: ColorChannel, table: ScaleTable): SharedScale;
2253
2276
  /**
2254
2277
  * #getter
2255
2278
  * each row panel's scale resolved against the values its field takes,
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
  },
@@ -2117,6 +2130,28 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
2117
2130
  },
2118
2131
  }));
2119
2132
  },
2133
+ /**
2134
+ * #getter
2135
+ * the reference row (`relativeTo`) repeated above the alignment, so it
2136
+ * stays in view while the rows compared against it scroll
2137
+ */
2138
+ get referenceTrackModels() {
2139
+ const { relativeTo } = self;
2140
+ const data = relativeTo === undefined ? undefined : self.columns.get(relativeTo);
2141
+ return relativeTo === undefined || data === undefined
2142
+ ? []
2143
+ : [
2144
+ {
2145
+ model: {
2146
+ id: 'reference-row',
2147
+ name: relativeTo,
2148
+ kind: 'reference',
2149
+ data,
2150
+ height: self.rowHeight,
2151
+ },
2152
+ },
2153
+ ];
2154
+ },
2120
2155
  get tracks() {
2121
2156
  return [
2122
2157
  ...this.adapterTrackModels,
@@ -2125,6 +2160,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
2125
2160
  // every computed track reads the alignment's columns, and a tree, a
2126
2161
  // GFF and a features panel draw a figure with none
2127
2162
  ...(self.numColumns > 0 ? this.computedTrackModels : []),
2163
+ ...this.referenceTrackModels,
2128
2164
  ];
2129
2165
  },
2130
2166
  /**
@@ -2888,7 +2924,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
2888
2924
  * #action
2889
2925
  */
2890
2926
  setColumnTracks(tracks) {
2891
- self.columnTracks.replace(tracks);
2927
+ self.columnTracks.replace(tracks.map(readColumnTrack));
2892
2928
  },
2893
2929
  /**
2894
2930
  * #action
@@ -2903,14 +2939,14 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
2903
2939
  * replace what the viewer's marks read from the row table
2904
2940
  */
2905
2941
  setEncodings(encodings) {
2906
- self.encodings.replace(encodings);
2942
+ self.encodings.replace(encodings.map(readEncoding));
2907
2943
  },
2908
2944
  /**
2909
2945
  * #action
2910
2946
  * replace the panels drawn between the tree and the alignment
2911
2947
  */
2912
2948
  setRowPanels(panels) {
2913
- self.rowPanels.replace(panels);
2949
+ self.rowPanels.replace(panels.map(readRowPanel));
2914
2950
  },
2915
2951
  /**
2916
2952
  * #action
@@ -3093,52 +3129,47 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
3093
3129
  * the categorical color keys drawn for this view, shared by the on-screen
3094
3130
  * legend overlay and the SVG export's reserved column. The domain overlay
3095
3131
  * produces the first, listing the `featureFill` scale where an encoding
3096
- * names one. Every field a row-table encoding or a row panel reads
3097
- * produces one more, so two channels over one field, or two strips over
3098
- * it, list that field once. A strip's `legend` names the title it lists
3099
- * under, so strips over eight fields with one set of colors list one
3100
- * legend between them
3132
+ * names one. Every scale a row-table encoding or a row panel reads
3133
+ * produces one more, keyed by its declaration, so two channels or two
3134
+ * strips declaring one scale list it once, and strips over eight fields
3135
+ * naming one `title` list one legend between them
3101
3136
  */
3102
3137
  get legends() {
3103
3138
  const { featureFillEncoding, fillPalette, visibleDomainTypes } = this;
3104
3139
  const byKey = new Map();
3140
+ const ids = new Set();
3105
3141
  // the accession palette keys on no field, so it takes a key of its own
3106
- const add = (field, entries, id) => {
3107
- if (entries.length === 0) {
3142
+ const add = (key, title, entries, overlay = false) => {
3143
+ const k = key ?? 'domains';
3144
+ if (entries.length === 0 || byKey.has(k)) {
3108
3145
  return;
3109
3146
  }
3110
- const key = field === undefined ? 'domains' : `field:${field}`;
3111
- const legend = byKey.get(key);
3112
- if (legend) {
3113
- const seen = new Set(legend.entries.map(e => e.id));
3114
- legend.entries.push(...entries.filter(e => !seen.has(e.id)));
3115
- }
3116
- else {
3117
- byKey.set(key, {
3118
- id: id ?? (field === undefined ? 'domains' : `rowData-${field}`),
3119
- title: field ?? 'Domains',
3120
- entries: [...entries],
3121
- });
3147
+ const base = overlay || key === undefined ? 'domains' : `rowData-${title}`;
3148
+ let id = base;
3149
+ for (let n = 2; ids.has(id); n++) {
3150
+ id = `${base}-${n}`;
3122
3151
  }
3152
+ ids.add(id);
3153
+ byKey.set(k, { id, title: title ?? 'Domains', entries });
3123
3154
  };
3124
3155
  // the overlay draws the domain key; with no columns it draws nothing,
3125
3156
  // and a features panel taking the same colors lists them instead
3126
3157
  if (self.actuallyShowDomains && self.numColumns > 0) {
3127
- add(featureFillEncoding?.field, featureFillEncoding
3158
+ add(featureFillEncoding?.key, featureFillEncoding?.title, featureFillEncoding
3128
3159
  ? featureFillEncoding.legend
3129
3160
  : visibleDomainTypes.map(d => ({
3130
3161
  id: d.accession,
3131
3162
  label: d.name,
3132
3163
  color: d.color ?? fillPalette[d.accession],
3133
- })), 'domains');
3164
+ })), true);
3134
3165
  }
3135
- for (const { channel, field, legend } of this.resolvedEncodings) {
3166
+ for (const { channel, key, title, legend } of this.resolvedEncodings) {
3136
3167
  if (!featureChannels.has(channel)) {
3137
- add(field, legend);
3168
+ add(key, title, legend);
3138
3169
  }
3139
3170
  }
3140
- for (const { legendTitle, legend } of this.rowPanelScales) {
3141
- add(legendTitle, legend);
3171
+ for (const { legendKey, legendTitle, legend } of this.rowPanelScales) {
3172
+ add(legendKey, legendTitle, legend);
3142
3173
  }
3143
3174
  return [...byKey.values()];
3144
3175
  },
@@ -3412,14 +3443,52 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
3412
3443
  * table or the encodings, never per row per frame.
3413
3444
  */
3414
3445
  get resolvedEncodings() {
3415
- const rows = Object.values(self.rowData);
3416
3446
  return self.encodings.map(encoding => ({
3417
3447
  ...encoding,
3418
- ...(featureChannels.has(encoding.channel)
3419
- ? resolveFeatureScale(encoding.field, encoding.scale, self.allAnnotations, self.filteredAnnotations)
3420
- : resolveScale(encoding.scale, rows.map(row => row?.[encoding.field]).filter(notEmpty))),
3448
+ ...this.scaleOf(encoding, featureChannels.has(encoding.channel) ? 'features' : 'rows'),
3421
3449
  }));
3422
3450
  },
3451
+ /**
3452
+ * #getter
3453
+ * every color object the encodings and the row panels declare, keyed
3454
+ * by declaration (`declarationKey`) and resolved once over the union
3455
+ * of the values its records' fields take, so a value two strips share
3456
+ * takes one color in both. A feature field reads every feature loaded,
3457
+ * so hiding a type in the filter dialog leaves the rest in their
3458
+ * colors, and its legend lists the values of the drawn features.
3459
+ */
3460
+ get resolvedScales() {
3461
+ const rows = Object.values(self.rowData);
3462
+ const featureValues = (list, field) => list.map(a => featureField(a, field)).filter(notEmpty);
3463
+ const use = (channel, table) => ({
3464
+ spec: channel,
3465
+ field: channel.field,
3466
+ table,
3467
+ ...(table === 'rows'
3468
+ ? {
3469
+ values: rows.map(row => row?.[channel.field]).filter(notEmpty),
3470
+ }
3471
+ : {
3472
+ values: featureValues(self.allAnnotations, channel.field),
3473
+ shown: featureValues(self.filteredAnnotations, channel.field),
3474
+ }),
3475
+ });
3476
+ return resolveShared([
3477
+ ...self.encodings.map(encoding => use(encoding, featureChannels.has(encoding.channel) ? 'features' : 'rows')),
3478
+ ...self.rowPanels.flatMap(panel => panel.kind === 'strip'
3479
+ ? [use(panel, 'rows')]
3480
+ : panel.encoding?.color
3481
+ ? [use(panel.encoding.color, 'features')]
3482
+ : []),
3483
+ ]);
3484
+ },
3485
+ /**
3486
+ * #method
3487
+ * the shared scale a record's color object resolves to
3488
+ */
3489
+ scaleOf(channel, table) {
3490
+ return this.resolvedScales.get(declarationKey({ spec: channel, field: channel.field, table }));
3491
+ },
3423
3492
  /**
3424
3493
  * #getter
3425
3494
  * each row panel's scale resolved against the values its field takes,
@@ -3434,7 +3503,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
3434
3503
  if (panel.kind === 'features') {
3435
3504
  const color = panel.encoding?.color;
3436
3505
  const encoding = color
3437
- ? resolveFeatureScale(color.field, color.scale, self.allAnnotations, annotations)
3506
+ ? { field: color.field, ...this.scaleOf(color, 'features') }
3438
3507
  : featureFillEncoding;
3439
3508
  const label = panel.encoding?.label;
3440
3509
  return {
@@ -3442,7 +3511,8 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
3442
3511
  x: panel.x,
3443
3512
  header: panel.header ?? '',
3444
3513
  field: encoding?.field,
3445
- legendTitle: encoding?.field,
3514
+ legendKey: encoding?.key,
3515
+ legendTitle: encoding?.title,
3446
3516
  colors: color
3447
3517
  ? featureColorMap(annotations, encoding, fillPalette)
3448
3518
  : this.featureColors,
@@ -3457,14 +3527,13 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
3457
3527
  })),
3458
3528
  };
3459
3529
  }
3460
- const { colorOf, legend } = resolveScale(panel.scale, Object.values(rowData)
3461
- .map(row => row?.[panel.field])
3462
- .filter(notEmpty));
3530
+ const { colorOf, legend, key, title } = this.scaleOf(panel, 'rows');
3463
3531
  return {
3464
3532
  kind: panel.kind,
3465
3533
  field: panel.field,
3466
3534
  header: panel.header ?? panel.field,
3467
- legendTitle: panel.legend ?? panel.field,
3535
+ legendKey: key,
3536
+ legendTitle: title,
3468
3537
  colors: colorByRow(rowData, panel.field, colorOf),
3469
3538
  legend,
3470
3539
  };