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
@@ -1,6 +1,7 @@
1
1
  import { columnLogoStack } from '../../sequenceLogo.ts'
2
2
  import { setFontSize } from '../../setFontSize.ts'
3
3
  import { contrastTextFn } from '../../util.ts'
4
+ import { tileColorFn } from '../msa/tileColor.ts'
4
5
  import { visibleColRange } from '../msa/visibleColRange.ts'
5
6
 
6
7
  import type { ColumnCounts } from '../../columnCounts.ts'
@@ -106,7 +107,7 @@ function drawArcs({
106
107
  function drawTextTrackContent({
107
108
  ctx,
108
109
  data,
109
- colorScheme,
110
+ colorAt,
110
111
  contrastText,
111
112
  bgColor,
112
113
  drawLetters,
@@ -117,7 +118,7 @@ function drawTextTrackContent({
117
118
  }: {
118
119
  ctx: RenderCtx
119
120
  data: string | undefined
120
- colorScheme: Record<string, string>
121
+ colorAt: (col: number, letter: string) => string | undefined
121
122
  contrastText: (color: string | undefined) => string
122
123
  bgColor: boolean
123
124
  drawLetters: boolean
@@ -135,9 +136,9 @@ function drawTextTrackContent({
135
136
 
136
137
  for (let i = 0; str && i < str.length; i++) {
137
138
  const letter = str[i]!
138
- const upper = letter.toUpperCase()
139
- const fill = bgColor ? colorScheme[upper] : undefined
140
- const x = (xStart + i) * colWidth
139
+ const col = xStart + i
140
+ const fill = bgColor ? colorAt(col, letter) : undefined
141
+ const x = col * colWidth
141
142
  if (fill) {
142
143
  ctx.fillStyle = fill
143
144
  ctx.fillRect(x, 0, colWidth, rowHeight)
@@ -402,15 +403,22 @@ export function drawTrackBlock({
402
403
  })
403
404
  break
404
405
  }
405
- case 'text': {
406
+ case 'text':
407
+ case 'reference': {
406
408
  const { bgColor, fontSize, rowHeight, showMsaLetters } = model
409
+ const scheme = customColorScheme ?? model.colorScheme
407
410
  ctx.textAlign = 'center'
408
411
  ctx.textBaseline = 'middle'
409
412
  setFontSize(ctx, fontSize)
410
413
  drawTextTrackContent({
411
414
  ctx,
412
415
  data,
413
- colorScheme: customColorScheme ?? model.colorScheme,
416
+ // the reference row takes its colors from the alignment's own
417
+ // scheme, per column under a dynamic one
418
+ colorAt:
419
+ kind === 'reference'
420
+ ? tileColorFn(model)
421
+ : (_col, letter) => scheme[letter.toUpperCase()],
414
422
  contrastText: contrastTextFn(theme),
415
423
  bgColor,
416
424
  drawLetters: showMsaLetters,
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useRef } from 'react'
1
+ import React, { useRef } from 'react'
2
2
 
3
3
  import { isAlive } from '@jbrowse/mobx-state-tree'
4
4
  import { observer } from 'mobx-react'
@@ -20,13 +20,11 @@ const TreeCanvas = observer(function ({ model }: { model: MsaViewModel }) {
20
20
  scrollY,
21
21
  highResScaleFactor,
22
22
  } = model
23
- const onScrollY = useCallback(
24
- (d: number) => {
25
- model.doScrollY(d)
26
- },
27
- [model],
28
- )
29
- const { onMouseDown, onMouseUp } = useWheelScroll({ ref, onScrollY })
23
+ const { onPointerDown, touchAction } = useWheelScroll({
24
+ ref,
25
+ model,
26
+ axes: 'y',
27
+ })
30
28
 
31
29
  const canvasWidth = effectiveTreeAreaWidth * highResScaleFactor
32
30
  const canvasHeight = height * highResScaleFactor
@@ -44,9 +42,9 @@ const TreeCanvas = observer(function ({ model }: { model: MsaViewModel }) {
44
42
  return (
45
43
  <div
46
44
  ref={ref}
47
- onMouseDown={onMouseDown}
48
- onMouseUp={onMouseUp}
45
+ onPointerDown={onPointerDown}
49
46
  style={{
47
+ touchAction,
50
48
  height,
51
49
  position: 'relative',
52
50
  width: effectiveTreeAreaWidth,
package/src/constants.ts CHANGED
@@ -4,6 +4,8 @@ import type { TreeOrder } from './types.ts'
4
4
  export const defaultRowHeight = 16
5
5
  export const defaultColWidth = 12
6
6
  export const defaultHeight = 550
7
+ // the dividers' thickness, between the tree and the alignment and under the view
8
+ export const resizeHandleWidth = 5
7
9
  export const defaultScrollX = 0
8
10
  export const defaultScrollY = 0
9
11
  export const defaultCurrentAlignment = 0
package/src/element.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { when } from 'mobx'
2
2
 
3
+ import { defaultHeight, resizeHandleWidth } from './constants.ts'
3
4
  import { mount } from './mount.tsx'
4
5
 
5
6
  import type { MSAViewerProps } from './components/MSAViewer.tsx'
@@ -46,8 +47,13 @@ const ownAttributes = [
46
47
  'theme',
47
48
  'reference-row',
48
49
  'tree-area-width',
50
+ 'loading',
49
51
  ] as const
50
52
 
53
+ // how far outside the viewport a lazy element starts mounting, so the viewer
54
+ // is drawing by the time the reader scrolls to it
55
+ const lazyMargin = '200px'
56
+
51
57
  // the attributes a <nightingale-manager> writes on the elements it holds
52
58
  const nightingaleAttributes = ['display-start', 'display-end', 'highlight']
53
59
 
@@ -87,13 +93,17 @@ function residueRange(model: MsaViewModel, row: string, viewport: Viewport) {
87
93
  /**
88
94
  * Registers a custom element that renders MSAViewer, for pages with no React.
89
95
  * Attributes: msa-url, tree-url, gff-url, color-scheme, height, hide-header,
90
- * theme, tree-area-width and reference-row. Properties: msa, tree and gff text,
91
- * features, colorScheme, highlights, columnTracks, residueMappings and
92
- * selection. The
93
- * colorScheme property takes a `{map}` the color-scheme attribute cannot hold,
94
- * and wins over the attribute while set. Events: cell-hover, cell-click,
95
- * viewport-change and selection-change, each with the MSAViewer callback's
96
- * value as detail.
96
+ * theme, tree-area-width, reference-row and loading. Properties: msa, tree and
97
+ * gff text, features, colorScheme, highlights, columnTracks, residueMappings
98
+ * and selection. The colorScheme property takes a `{domain, range}` the
99
+ * color-scheme attribute cannot hold, and wins over the attribute while set. Events:
100
+ * cell-hover, cell-click, viewport-change and selection-change, each with the
101
+ * MSAViewer callback's value as detail.
102
+ *
103
+ * `loading="lazy"` mounts the viewer once the element comes within 200 pixels
104
+ * of the viewport, as `<img loading="lazy">` loads its image. Until then the
105
+ * element shows its own children, such as an `<img>` of the first frame, and
106
+ * reserves the viewer's height.
97
107
  *
98
108
  * Inside a <nightingale-manager>, the element registers with it and speaks its
99
109
  * protocol: it follows the display-start, display-end and highlight attributes
@@ -110,6 +120,7 @@ export function defineMsaElement(tagName = 'jbrowse-msa') {
110
120
  static observedAttributes = [...ownAttributes, ...nightingaleAttributes]
111
121
 
112
122
  #viewer?: MountedViewer
123
+ #nearby?: IntersectionObserver
113
124
  #model?: MsaViewModel
114
125
  #manager?: NightingaleManager
115
126
  #cancelRange?: () => void
@@ -289,7 +300,22 @@ export function defineMsaElement(tagName = 'jbrowse-msa') {
289
300
  if (!this.style.display) {
290
301
  this.style.display = 'block'
291
302
  }
292
- this.#viewer = mount(this, this.#props())
303
+ if (this.#lazy) {
304
+ const height =
305
+ numberAttribute(this.getAttribute('height')) ?? defaultHeight
306
+ this.style.minHeight = `${height + resizeHandleWidth}px`
307
+ this.#nearby = new IntersectionObserver(
308
+ entries => {
309
+ if (entries.some(entry => entry.isIntersecting)) {
310
+ this.#mount()
311
+ }
312
+ },
313
+ { rootMargin: lazyMargin },
314
+ )
315
+ this.#nearby.observe(this)
316
+ } else {
317
+ this.#mount()
318
+ }
293
319
  const manager = this.closest<NightingaleManager>('nightingale-manager')
294
320
  if (manager) {
295
321
  void customElements.whenDefined('nightingale-manager').then(() => {
@@ -301,7 +327,22 @@ export function defineMsaElement(tagName = 'jbrowse-msa') {
301
327
  }
302
328
  }
303
329
 
330
+ get #lazy() {
331
+ return (
332
+ this.getAttribute('loading') === 'lazy' &&
333
+ typeof IntersectionObserver !== 'undefined'
334
+ )
335
+ }
336
+
337
+ #mount() {
338
+ this.#nearby?.disconnect()
339
+ this.#nearby = undefined
340
+ this.#viewer ??= mount(this, this.#props())
341
+ }
342
+
304
343
  disconnectedCallback() {
344
+ this.#nearby?.disconnect()
345
+ this.#nearby = undefined
305
346
  this.#manager?.unregister(this)
306
347
  this.#manager = undefined
307
348
  this.#cancelRange?.()
@@ -323,6 +364,10 @@ export function defineMsaElement(tagName = 'jbrowse-msa') {
323
364
  }
324
365
  } else if (name === 'highlight') {
325
366
  this.#followHighlight()
367
+ } else if (name === 'loading') {
368
+ if (this.#nearby && !this.#lazy) {
369
+ this.#mount()
370
+ }
326
371
  } else {
327
372
  this.#viewer?.update(this.#props())
328
373
  }
@@ -1,12 +1,8 @@
1
- import { notEmpty } from '@jbrowse/core/util'
2
-
3
1
  import { packDomainLanes } from './components/msa/packDomainLanes.ts'
4
2
  import { defaultFeaturePanelWidth } from './constants.ts'
5
3
  import { featureField } from './featureFields.ts'
6
- import { resolveScale } from './scales.ts'
7
4
  import { outlineColor } from './util.ts'
8
5
 
9
- import type { ScaleSpec } from './scales.ts'
10
6
  import type {
11
7
  Annotation,
12
8
  DomainBand,
@@ -16,7 +12,7 @@ import type {
16
12
  } from './types.ts'
17
13
 
18
14
  // the fill of a span under a scale that gives its value no color, such as a
19
- // gene a `map` over gene names leaves out
15
+ // gene a `domain` of gene names leaves out
20
16
  const UNSCALED_FEATURE = '#d9d9d9'
21
17
 
22
18
  // the fraction of a span a following one may cover before it takes a lane of
@@ -247,23 +243,3 @@ export function featurePanelSpans({
247
243
  ]),
248
244
  )
249
245
  }
250
-
251
- // a scale over the values a field takes across every feature loaded, so hiding
252
- // a type in the filter dialog leaves the rest in their colors, and a legend of
253
- // the values the features drawn carry
254
- export function resolveFeatureScale(
255
- field: string,
256
- scale: ScaleSpec | undefined,
257
- annotations: Annotation[],
258
- drawn: Annotation[],
259
- ) {
260
- const valuesOf = (list: Annotation[]) =>
261
- list.map(a => featureField(a, field)).filter(notEmpty)
262
- const resolved = resolveScale(scale, valuesOf(annotations))
263
- const shown = new Set(valuesOf(drawn))
264
- return {
265
- field,
266
- ...resolved,
267
- legend: resolved.legend.filter(entry => shown.has(entry.id)),
268
- }
269
- }
@@ -72,10 +72,22 @@ export const namedPalettes: Record<string, readonly string[]> = {
72
72
  ],
73
73
  }
74
74
 
75
+ // the spellings jbrowse-components' `paletteFromSpec` takes for two of them
76
+ const aliases: Record<string, string> = {
77
+ okabeIto: 'okabeito',
78
+ tableau10: 'tableau',
79
+ }
80
+
75
81
  /**
76
82
  * The colors a palette name asks for. `ggplot`, an unknown name, and no name at
77
83
  * all give undefined, which `createPaletteMap` answers with the hue ramp.
78
84
  */
79
85
  export function paletteNamed(name?: string) {
80
- return name === undefined ? undefined : namedPalettes[name]
86
+ return name === undefined
87
+ ? undefined
88
+ : Object.hasOwn(namedPalettes, name)
89
+ ? namedPalettes[name]
90
+ : Object.hasOwn(aliases, name)
91
+ ? namedPalettes[aliases[name]!]
92
+ : undefined
81
93
  }
@@ -0,0 +1,54 @@
1
+ // copied from jbrowse-components packages/core/src/util/groupKeys.ts, which
2
+ // @jbrowse/core 5.0.0-beta.9 ships but the hosts the plugin runs on do not
3
+
4
+ const NUMERIC = /^-?\d+(\.\d+)?$/
5
+ const DIGIT_RUNS = /\d+|\D+/g
6
+
7
+ function compareRuns(a: string, b: string) {
8
+ const runsA = a.match(DIGIT_RUNS) ?? []
9
+ const runsB = b.match(DIGIT_RUNS) ?? []
10
+ const shared = Math.min(runsA.length, runsB.length)
11
+ for (let i = 0; i < shared; i++) {
12
+ const x = runsA[i]!
13
+ const y = runsB[i]!
14
+ if (x === y) {
15
+ continue
16
+ }
17
+ if (/^\d/.test(x) && /^\d/.test(y)) {
18
+ const nx = Number(x)
19
+ const ny = Number(y)
20
+ if (nx !== ny) {
21
+ return nx < ny ? -1 : 1
22
+ }
23
+ return x.length < y.length ? -1 : 1
24
+ }
25
+ return x < y ? -1 : 1
26
+ }
27
+ return runsA.length - runsB.length
28
+ }
29
+
30
+ /**
31
+ * Numbers first by value, then every other value run by run with digit runs
32
+ * by magnitude, so `2` before `10`, `chr2` before `chr10` and `2.3.2.1a`
33
+ * before `2.3.4.4b`. The empty value sorts last.
34
+ */
35
+ export function compareGroupKeys(a: string, b: string) {
36
+ if (a === b) {
37
+ return 0
38
+ }
39
+ if (a === '' || b === '') {
40
+ return a === '' ? 1 : -1
41
+ }
42
+ const numA = NUMERIC.test(a)
43
+ const numB = NUMERIC.test(b)
44
+ if (numA !== numB) {
45
+ return numA ? -1 : 1
46
+ }
47
+ if (numA) {
48
+ const diff = Number(a) - Number(b)
49
+ if (diff !== 0) {
50
+ return diff < 0 ? -1 : 1
51
+ }
52
+ }
53
+ return compareRuns(a, b)
54
+ }
package/src/index.ts CHANGED
@@ -51,8 +51,19 @@ export type { InterProScanResults } from 'msa-parsers'
51
51
  export type { ColumnCounts } from './columnCounts.ts'
52
52
  export type { ColumnStats } from './columnStats.ts'
53
53
  export type { ZoomSnapshot } from './zoomSnapshot.ts'
54
- // the scale an encoding reads its field through, and what resolving one gives
55
- export type { ResolvedScale, ScaleSpec } from './scales.ts'
54
+ // the color object a channel reads its field through, and what resolving one
55
+ // gives
56
+ export type {
57
+ ColorChannel,
58
+ ColorSpec,
59
+ LegacyColumnTrack,
60
+ LegacyEncoding,
61
+ LegacyRowPanel,
62
+ LegacyScale,
63
+ ResolvedScale,
64
+ ScaleTable,
65
+ SharedScale,
66
+ } from './scales.ts'
56
67
  export type {
57
68
  Accession,
58
69
  Annotation,