jbrowse-plugin-msaview 5.5.0 → 5.7.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 (126) hide show
  1. package/dist/MsaViewPanel/afterCreateAutoruns.js +1 -1
  2. package/dist/MsaViewPanel/connectedDomains.d.ts +29 -11
  3. package/dist/MsaViewPanel/connectedDomains.js +47 -20
  4. package/dist/MsaViewPanel/connectedDomains.test.js +63 -24
  5. package/dist/MsaViewPanel/domainTrack.d.ts +7 -8
  6. package/dist/MsaViewPanel/domainTrack.js +5 -18
  7. package/dist/MsaViewPanel/domainTrack.test.js +29 -27
  8. package/dist/MsaViewPanel/loadProteinDomains.js +2 -1
  9. package/dist/MsaViewPanel/loadProteinDomains.test.js +2 -0
  10. package/dist/MsaViewPanel/model.d.ts +84 -48
  11. package/dist/MsaViewPanel/model.js +5 -5
  12. package/dist/MsaViewPanel/model.test.js +33 -21
  13. package/dist/chunks/{AboutDialog-HCCCEJ6V.js → AboutDialog-ZHJOECNZ.js} +2 -2
  14. package/dist/chunks/{AboutDialog-HCCCEJ6V.js.map → AboutDialog-ZHJOECNZ.js.map} +1 -1
  15. package/dist/chunks/AnnotationFileDialog-6LTHZI64.js +2 -0
  16. package/dist/chunks/AnnotationFileDialog-6LTHZI64.js.map +7 -0
  17. package/dist/chunks/{ExportSVGDialog-YUFLYBYW.js → ExportSVGDialog-VP5LUY7X.js} +2 -2
  18. package/dist/chunks/{ExportSVGDialog-YUFLYBYW.js.map → ExportSVGDialog-VP5LUY7X.js.map} +1 -1
  19. package/dist/chunks/{FeatureDialog-NBDB5SWU.js → FeatureDialog-YEOUA5YS.js} +2 -2
  20. package/dist/chunks/{FeatureDialog-NBDB5SWU.js.map → FeatureDialog-YEOUA5YS.js.map} +3 -3
  21. package/dist/chunks/GapThresholdDialog-3MOBT6NY.js +2 -0
  22. package/dist/chunks/GapThresholdDialog-3MOBT6NY.js.map +7 -0
  23. package/dist/chunks/GoToDialog-2L4KUO6R.js +2 -0
  24. package/dist/chunks/GoToDialog-2L4KUO6R.js.map +7 -0
  25. package/dist/chunks/{HelpDialog-UCUYPCOI.js → HelpDialog-3KGMHUYG.js} +2 -2
  26. package/dist/chunks/{HelpDialog-UCUYPCOI.js.map → HelpDialog-3KGMHUYG.js.map} +1 -1
  27. package/dist/chunks/{LaunchMsaViewDialog-BG4MX2BW.js → LaunchMsaViewDialog-GR4BB3W2.js} +2 -2
  28. package/dist/chunks/{LaunchMsaViewDialog-BG4MX2BW.js.map → LaunchMsaViewDialog-GR4BB3W2.js.map} +1 -1
  29. package/dist/chunks/MetadataDialog-LAVES424.js +2 -0
  30. package/dist/chunks/MetadataDialog-LAVES424.js.map +7 -0
  31. package/dist/chunks/MsaViewPanel-3R4SLB6M.js +2 -0
  32. package/dist/chunks/{MsaViewPanel-XSXFJWWY.js.map → MsaViewPanel-3R4SLB6M.js.map} +1 -1
  33. package/dist/chunks/{TrackInfoDialog-YXCIGFYM.js → TrackInfoDialog-RJDJ6NVE.js} +2 -2
  34. package/dist/chunks/{TrackInfoDialog-YXCIGFYM.js.map → TrackInfoDialog-RJDJ6NVE.js.map} +1 -1
  35. package/dist/chunks/TreeNodeInfoDialog-IWH7HU7R.js +2 -0
  36. package/dist/chunks/{TreeNodeInfoDialog-QCWDJTM4.js.map → TreeNodeInfoDialog-IWH7HU7R.js.map} +2 -2
  37. package/dist/chunks/chunk-35FE7DCA.js +6 -0
  38. package/dist/chunks/chunk-35FE7DCA.js.map +7 -0
  39. package/dist/chunks/chunk-6GRHBYHB.js +6 -0
  40. package/dist/chunks/chunk-6GRHBYHB.js.map +7 -0
  41. package/dist/chunks/chunk-BVAC5762.js +164 -0
  42. package/dist/chunks/chunk-BVAC5762.js.map +7 -0
  43. package/dist/chunks/chunk-CMRWVG7E.js +2 -0
  44. package/dist/chunks/chunk-CMRWVG7E.js.map +7 -0
  45. package/dist/chunks/chunk-DFVHVWTM.js +2 -0
  46. package/dist/chunks/chunk-DFVHVWTM.js.map +7 -0
  47. package/dist/chunks/{chunk-4TEPHD3L.js → chunk-DYDEKAIN.js} +2 -2
  48. package/dist/chunks/{chunk-35FWLG3N.js → chunk-EJCJ3TTF.js} +1 -1
  49. package/dist/chunks/{chunk-35FWLG3N.js.map → chunk-EJCJ3TTF.js.map} +1 -1
  50. package/dist/chunks/{chunk-KSCGTF4R.js → chunk-EKTRLQV7.js} +2 -2
  51. package/dist/chunks/{chunk-666BV764.js → chunk-GEFXJOLW.js} +2 -2
  52. package/dist/chunks/{chunk-666BV764.js.map → chunk-GEFXJOLW.js.map} +1 -1
  53. package/dist/chunks/{chunk-GAY2PLS6.js → chunk-HO3AGOGX.js} +1 -1
  54. package/dist/chunks/{chunk-GAY2PLS6.js.map → chunk-HO3AGOGX.js.map} +1 -1
  55. package/dist/chunks/chunk-K55ZVN73.js +5 -0
  56. package/dist/chunks/chunk-K55ZVN73.js.map +7 -0
  57. package/dist/chunks/chunk-KYQ2SO7B.js +2 -0
  58. package/dist/chunks/{chunk-QQKPT6EL.js.map → chunk-KYQ2SO7B.js.map} +1 -1
  59. package/dist/chunks/chunk-L4YUAU73.js +8 -0
  60. package/dist/chunks/chunk-L4YUAU73.js.map +7 -0
  61. package/dist/chunks/{chunk-NLAW2OYS.js → chunk-M2AAIXY5.js} +2 -2
  62. package/dist/chunks/chunk-NGMLSBBJ.js +2 -0
  63. package/dist/chunks/chunk-NGMLSBBJ.js.map +7 -0
  64. package/dist/chunks/{chunk-5OSSOQH4.js → chunk-OG5MRAUS.js} +1 -1
  65. package/dist/chunks/{chunk-5OSSOQH4.js.map → chunk-OG5MRAUS.js.map} +1 -1
  66. package/dist/chunks/{chunk-AQ2THRTR.js → chunk-QWXSEUZI.js} +2 -2
  67. package/dist/chunks/chunk-TIHSPFDA.js +2 -0
  68. package/dist/chunks/{chunk-XTQQNBQ5.js.map → chunk-TIHSPFDA.js.map} +2 -2
  69. package/dist/chunks/chunk-UVY5VU6U.js +2 -0
  70. package/dist/chunks/chunk-UVY5VU6U.js.map +7 -0
  71. package/dist/chunks/dist-LYE6BRGC.js +2 -0
  72. package/dist/chunks/{dist-GXYZN4ZR.js → dist-S26YAMK2.js} +1 -1
  73. package/dist/chunks/{dist-GXYZN4ZR.js.map → dist-S26YAMK2.js.map} +1 -1
  74. package/dist/chunks/{model-7RBDANXP.js → model-IH7IQHGD.js} +6 -6
  75. package/dist/chunks/model-IH7IQHGD.js.map +7 -0
  76. package/dist/chunks/{renderToSvg-5DMIQ72S.js → renderToSvg-HKGHZ7XL.js} +2 -2
  77. package/dist/jbrowse-plugin-msaview.esm.js +2 -2
  78. package/dist/jbrowse-plugin-msaview.esm.js.map +1 -1
  79. package/dist/utils/domainCache.js +17 -9
  80. package/dist/utils/ncbiDomains.d.ts +4 -1
  81. package/dist/utils/ncbiDomains.js +1 -0
  82. package/dist/version.d.ts +1 -1
  83. package/dist/version.js +1 -1
  84. package/package.json +2 -2
  85. package/src/MsaViewPanel/afterCreateAutoruns.ts +1 -1
  86. package/src/MsaViewPanel/connectedDomains.test.ts +67 -24
  87. package/src/MsaViewPanel/connectedDomains.ts +70 -26
  88. package/src/MsaViewPanel/domainTrack.test.ts +29 -27
  89. package/src/MsaViewPanel/domainTrack.ts +7 -20
  90. package/src/MsaViewPanel/loadProteinDomains.test.ts +2 -0
  91. package/src/MsaViewPanel/loadProteinDomains.ts +13 -11
  92. package/src/MsaViewPanel/model.test.ts +33 -21
  93. package/src/MsaViewPanel/model.ts +7 -7
  94. package/src/utils/domainCache.ts +17 -9
  95. package/src/utils/ncbiDomains.ts +5 -1
  96. package/src/version.ts +1 -1
  97. package/dist/chunks/AnnotationFileDialog-CPJ4X7XW.js +0 -2
  98. package/dist/chunks/AnnotationFileDialog-CPJ4X7XW.js.map +0 -7
  99. package/dist/chunks/MetadataDialog-DSA3545E.js +0 -2
  100. package/dist/chunks/MetadataDialog-DSA3545E.js.map +0 -7
  101. package/dist/chunks/MsaViewPanel-XSXFJWWY.js +0 -2
  102. package/dist/chunks/TreeNodeInfoDialog-QCWDJTM4.js +0 -2
  103. package/dist/chunks/chunk-ASUKJX5J.js +0 -2
  104. package/dist/chunks/chunk-ASUKJX5J.js.map +0 -7
  105. package/dist/chunks/chunk-F726IWCW.js +0 -2
  106. package/dist/chunks/chunk-F726IWCW.js.map +0 -7
  107. package/dist/chunks/chunk-FFHB7WP7.js +0 -18
  108. package/dist/chunks/chunk-FFHB7WP7.js.map +0 -7
  109. package/dist/chunks/chunk-JDQUBCHM.js +0 -2
  110. package/dist/chunks/chunk-JDQUBCHM.js.map +0 -7
  111. package/dist/chunks/chunk-QQKPT6EL.js +0 -2
  112. package/dist/chunks/chunk-WMDOEOAH.js +0 -6
  113. package/dist/chunks/chunk-WMDOEOAH.js.map +0 -7
  114. package/dist/chunks/chunk-XTQQNBQ5.js +0 -2
  115. package/dist/chunks/chunk-XXCNVFGP.js +0 -11
  116. package/dist/chunks/chunk-XXCNVFGP.js.map +0 -7
  117. package/dist/chunks/chunk-ZQSWX57C.js +0 -2
  118. package/dist/chunks/chunk-ZQSWX57C.js.map +0 -7
  119. package/dist/chunks/dist-GZKCGB2N.js +0 -2
  120. package/dist/chunks/model-7RBDANXP.js.map +0 -7
  121. /package/dist/chunks/{chunk-4TEPHD3L.js.map → chunk-DYDEKAIN.js.map} +0 -0
  122. /package/dist/chunks/{chunk-KSCGTF4R.js.map → chunk-EKTRLQV7.js.map} +0 -0
  123. /package/dist/chunks/{chunk-NLAW2OYS.js.map → chunk-M2AAIXY5.js.map} +0 -0
  124. /package/dist/chunks/{chunk-AQ2THRTR.js.map → chunk-QWXSEUZI.js.map} +0 -0
  125. /package/dist/chunks/{dist-GZKCGB2N.js.map → dist-LYE6BRGC.js.map} +0 -0
  126. /package/dist/chunks/{renderToSvg-5DMIQ72S.js.map → renderToSvg-HKGHZ7XL.js.map} +0 -0
@@ -393,7 +393,7 @@ export function runCleanup() {
393
393
  export function showDomainsOnGenomeIfAsked(self) {
394
394
  if (self.domainsOnGenome &&
395
395
  self.connectedView &&
396
- self.connectedDomains.length > 0) {
396
+ self.connectedDomains.features.length > 0) {
397
397
  self.showDomainsOnGenome();
398
398
  self.setDomainsOnGenome(false);
399
399
  }
@@ -1,15 +1,16 @@
1
1
  import type { GenomeRegion, SeqPosModel } from './msaCoordToGenomeCoord';
2
2
  import type { Annotation } from 'react-msaview';
3
- /**
4
- * One accession the alignment draws on the query row, on the genome. A
5
- * cross-plugin contract: jbrowse-plugin-protein3d reads it off the view.
6
- */
3
+ export type DomainKind = 'domain' | 'site';
4
+ /** One accession the alignment draws on the query row. */
7
5
  export interface ConnectedDomain {
8
- /** the label the alignment's legend shows for the domain's accession */
6
+ /** the label the alignment's legend shows for the accession */
9
7
  name: string;
10
8
  accession: string;
11
- /** the fill the alignment draws the domain in */
12
- color: string;
9
+ /**
10
+ * `site` for a set of functional residues (a CDD Site, a GFF `*_site`),
11
+ * `domain` for anything else
12
+ */
13
+ kind: DomainKind;
13
14
  /**
14
15
  * 0-based half-open, ascending, contiguous bases merged: one region per exon
15
16
  * piece of a domain, or per run of a site's scattered residues, in the
@@ -17,6 +18,24 @@ export interface ConnectedDomain {
17
18
  */
18
19
  regions: GenomeRegion[];
19
20
  }
21
+ /** A categorical color object, spelt as a JBrowse display's `color` config. */
22
+ export interface DomainColor {
23
+ field: 'accession';
24
+ domain: string[];
25
+ range: string[];
26
+ labels: string[];
27
+ }
28
+ export interface ConnectedDomains {
29
+ /** largest first by residues covered */
30
+ features: ConnectedDomain[];
31
+ color: DomainColor;
32
+ }
33
+ /**
34
+ * `site` for CDD's GenPept `Site` key, a Sequence Ontology `*_site` type,
35
+ * InterPro's `Binding_site`, `Active_site` and `Conserved_site`, and UniProt's
36
+ * spelled-out `Binding site`
37
+ */
38
+ export declare function annotationKind(featureType: string | undefined): DomainKind;
20
39
  export interface DomainSource extends SeqPosModel {
21
40
  showDomains: boolean;
22
41
  querySeqName: string;
@@ -33,8 +52,7 @@ export interface DomainSource extends SeqPosModel {
33
52
  export declare function residuesToGenomeRegions(model: SeqPosModel, positions: Iterable<number>): GenomeRegion[];
34
53
  /**
35
54
  * The query row's domains as the alignment shows them, one per accession, so
36
- * a CDD site's scattered residues are one entry. Largest first by residues
37
- * covered, so a site painted later lands on the domain it sits in. Empty
38
- * while the domain overlay is off.
55
+ * a CDD site's scattered residues are one feature. Empty while the domain
56
+ * overlay is off.
39
57
  */
40
- export declare function connectedDomains(model: DomainSource): ConnectedDomain[];
58
+ export declare function connectedDomains(model: DomainSource): ConnectedDomains;
@@ -1,4 +1,24 @@
1
+ /**
2
+ * The query row's domains on the genome, as data plus one color object. A
3
+ * cross-plugin contract: jbrowse-plugin-protein3d reads it off the view.
4
+ *
5
+ * In grammar-of-graphics terms, `features` is the data and `kind` the field a
6
+ * consumer filters its layers on (protein3d draws `domain` as cartoon and
7
+ * `site` as side chains). `color` is the color channel: a categorical scale
8
+ * over `accession`, whose `domain` lists the accessions, `range` the fills
9
+ * the alignment draws them in, and `labels` what a guide (a legend) names
10
+ * them. Every surface reads that one object: the genome track's display, the
11
+ * structure's layers, its overview rows and each legend.
12
+ */
1
13
  import { seqPosToGenomeRegions } from './msaCoordToGenomeCoord';
14
+ /**
15
+ * `site` for CDD's GenPept `Site` key, a Sequence Ontology `*_site` type,
16
+ * InterPro's `Binding_site`, `Active_site` and `Conserved_site`, and UniProt's
17
+ * spelled-out `Binding site`
18
+ */
19
+ export function annotationKind(featureType) {
20
+ return featureType && /(^|[_ ])site$/i.test(featureType) ? 'site' : 'domain';
21
+ }
2
22
  function mergeAdjacent(regions) {
3
23
  const merged = [];
4
24
  for (const region of regions.toSorted((a, b) => a.start - b.start)) {
@@ -25,25 +45,23 @@ export function residuesToGenomeRegions(model, positions) {
25
45
  }
26
46
  /**
27
47
  * The query row's domains as the alignment shows them, one per accession, so
28
- * a CDD site's scattered residues are one entry. Largest first by residues
29
- * covered, so a site painted later lands on the domain it sits in. Empty
30
- * while the domain overlay is off.
48
+ * a CDD site's scattered residues are one feature. Empty while the domain
49
+ * overlay is off.
31
50
  */
32
51
  export function connectedDomains(model) {
33
- if (!model.showDomains) {
34
- return [];
35
- }
36
52
  const byAccession = new Map();
37
- for (const annotation of model.annotationsByRow[model.querySeqName] ?? []) {
38
- const list = byAccession.get(annotation.accession);
39
- if (list) {
40
- list.push(annotation);
41
- }
42
- else {
43
- byAccession.set(annotation.accession, [annotation]);
53
+ if (model.showDomains) {
54
+ for (const annotation of model.annotationsByRow[model.querySeqName] ?? []) {
55
+ const list = byAccession.get(annotation.accession);
56
+ if (list) {
57
+ list.push(annotation);
58
+ }
59
+ else {
60
+ byAccession.set(annotation.accession, [annotation]);
61
+ }
44
62
  }
45
63
  }
46
- return [...byAccession]
64
+ const placed = [...byAccession]
47
65
  .flatMap(([accession, annotations]) => {
48
66
  // annotations count residues 1-based inclusive
49
67
  const positions = new Set();
@@ -53,22 +71,31 @@ export function connectedDomains(model) {
53
71
  }
54
72
  }
55
73
  const first = annotations[0];
56
- const color = model.featureColors.get(first)?.fill;
74
+ const fill = model.featureColors.get(first)?.fill;
57
75
  const regions = residuesToGenomeRegions(model, positions);
58
- return color && regions.length > 0
76
+ return fill && regions.length > 0
59
77
  ? [
60
78
  {
61
- domain: {
79
+ feature: {
62
80
  name: model.annotationTypes.get(accession)?.name ?? first.name,
63
81
  accession,
64
- color,
82
+ kind: annotationKind(first.featureType),
65
83
  regions,
66
84
  },
85
+ fill,
67
86
  residues: positions.size,
68
87
  },
69
88
  ]
70
89
  : [];
71
90
  })
72
- .toSorted((a, b) => b.residues - a.residues)
73
- .map(({ domain }) => domain);
91
+ .toSorted((a, b) => b.residues - a.residues);
92
+ return {
93
+ features: placed.map(p => p.feature),
94
+ color: {
95
+ field: 'accession',
96
+ domain: placed.map(p => p.feature.accession),
97
+ range: placed.map(p => p.fill),
98
+ labels: placed.map(p => p.feature.name),
99
+ },
100
+ };
74
101
  }
@@ -1,6 +1,6 @@
1
1
  import { genomeToTranscriptSeqMapping } from 'g2p_mapper';
2
2
  import { describe, expect, test } from 'vitest';
3
- import { connectedDomains } from './connectedDomains';
3
+ import { annotationKind, connectedDomains } from './connectedDomains';
4
4
  // five codons read from 207 down: 207-205, 204-202, 201-200 then 106 across
5
5
  // the intron, 105-103, 102-100
6
6
  const reverseTranscript = genomeToTranscriptSeqMapping({
@@ -39,38 +39,48 @@ function source(annotations, overrides = {}) {
39
39
  }
40
40
  describe('connectedDomains on a reverse-strand transcript', () => {
41
41
  test('a domain crossing the exon boundary is one region per exon piece', () => {
42
- expect(connectedDomains(source([annotation('site', 2, 4)]))).toEqual([
43
- {
44
- name: 'legend site',
45
- accession: 'site',
46
- color: 'fill-site',
47
- regions: [
48
- { refName: 'chr1', start: 103, end: 107 },
49
- { refName: 'chr1', start: 200, end: 205 },
50
- ],
42
+ expect(connectedDomains(source([annotation('site', 2, 4)]))).toEqual({
43
+ features: [
44
+ {
45
+ name: 'legend site',
46
+ accession: 'site',
47
+ kind: 'domain',
48
+ regions: [
49
+ { refName: 'chr1', start: 103, end: 107 },
50
+ { refName: 'chr1', start: 200, end: 205 },
51
+ ],
52
+ },
53
+ ],
54
+ color: {
55
+ field: 'accession',
56
+ domain: ['site'],
57
+ range: ['fill-site'],
58
+ labels: ['legend site'],
51
59
  },
52
- ]);
60
+ });
53
61
  });
54
- test('the longest comes first, so a nested site paints over it', () => {
55
- const domains = connectedDomains(source([annotation('site', 2, 4), annotation('domain', 1, 5)]));
56
- expect(domains.map(d => d.accession)).toEqual(['domain', 'site']);
57
- expect(domains[0].regions).toEqual([
62
+ test('the longest comes first, in the features and the color domain alike', () => {
63
+ const { features, color } = connectedDomains(source([annotation('site', 2, 4), annotation('domain', 1, 5)]));
64
+ expect(features.map(d => d.accession)).toEqual(['domain', 'site']);
65
+ expect(color.domain).toEqual(['domain', 'site']);
66
+ expect(color.range).toEqual(['fill-domain', 'fill-site']);
67
+ expect(features[0].regions).toEqual([
58
68
  { refName: 'chr1', start: 100, end: 107 },
59
69
  { refName: 'chr1', start: 200, end: 208 },
60
70
  ]);
61
71
  });
62
72
  test('a scattered site is one entry, its residues merged where they touch', () => {
63
- const domains = connectedDomains(source([
73
+ const { features } = connectedDomains(source([
64
74
  annotation('small', 4, 5),
65
75
  annotation('heme', 1, 1),
66
76
  annotation('heme', 3, 3),
67
77
  annotation('heme', 4, 4),
68
78
  ]));
69
- expect(domains.map(d => d.accession)).toEqual(['heme', 'small']);
70
- expect(domains[0]).toEqual({
79
+ expect(features.map(d => d.accession)).toEqual(['heme', 'small']);
80
+ expect(features[0]).toEqual({
71
81
  name: 'legend heme',
72
82
  accession: 'heme',
73
- color: 'fill-heme',
83
+ kind: 'domain',
74
84
  regions: [
75
85
  { refName: 'chr1', start: 103, end: 107 },
76
86
  { refName: 'chr1', start: 200, end: 202 },
@@ -79,14 +89,14 @@ describe('connectedDomains on a reverse-strand transcript', () => {
79
89
  });
80
90
  });
81
91
  test('a trimmed query row shifts by querySeqOffset', () => {
82
- const [domain] = connectedDomains(source([annotation('site', 1, 2)], { querySeqOffset: 1 }));
92
+ const [domain] = connectedDomains(source([annotation('site', 1, 2)], { querySeqOffset: 1 })).features;
83
93
  expect(domain.regions).toEqual([
84
94
  { refName: 'chr1', start: 106, end: 107 },
85
95
  { refName: 'chr1', start: 200, end: 205 },
86
96
  ]);
87
97
  });
88
98
  test('residues past the transcript drop out, and a domain with none left', () => {
89
- expect(connectedDomains(source([annotation('tail', 5, 9), annotation('off', 7, 9)]))).toEqual([
99
+ expect(connectedDomains(source([annotation('tail', 5, 9), annotation('off', 7, 9)])).features).toEqual([
90
100
  expect.objectContaining({
91
101
  accession: 'tail',
92
102
  regions: [{ refName: 'chr1', start: 100, end: 103 }],
@@ -95,14 +105,43 @@ describe('connectedDomains on a reverse-strand transcript', () => {
95
105
  });
96
106
  test('only the query row, and nothing while the overlay is off', () => {
97
107
  const annotations = [annotation('other', 1, 5, 'Mouse')];
98
- expect(connectedDomains(source(annotations))).toEqual([]);
99
- expect(connectedDomains(source([annotation('domain', 1, 5)], { showDomains: false }))).toEqual([]);
108
+ expect(connectedDomains(source(annotations)).features).toEqual([]);
109
+ expect(connectedDomains(source([annotation('domain', 1, 5)], { showDomains: false }))).toEqual({
110
+ features: [],
111
+ color: { field: 'accession', domain: [], range: [], labels: [] },
112
+ });
100
113
  });
101
114
  });
102
115
  test('a MAF row maps one base per residue', () => {
103
116
  const [domain] = connectedDomains(source([annotation('d', 3, 6)], {
104
117
  transcriptToMsaMap: undefined,
105
118
  mafRegion: { refName: 'chr2', start: 50, end: 100, assemblyName: 'hg38' },
106
- }));
119
+ })).features;
107
120
  expect(domain.regions).toEqual([{ refName: 'chr2', start: 52, end: 56 }]);
108
121
  });
122
+ test('an entry is a site when its featureType names one', () => {
123
+ const { features } = connectedDomains(source([
124
+ { ...annotation('heme', 2, 2), featureType: 'Site' },
125
+ { ...annotation('globin', 1, 5), featureType: 'Region' },
126
+ annotation('gff', 1, 4),
127
+ ]));
128
+ expect(features.map(d => [d.accession, d.kind])).toEqual([
129
+ ['globin', 'domain'],
130
+ ['gff', 'domain'],
131
+ ['heme', 'site'],
132
+ ]);
133
+ });
134
+ test.each([
135
+ ['Site', 'site'],
136
+ ['binding_site', 'site'],
137
+ ['Active_site', 'site'],
138
+ ['Conserved_site', 'site'],
139
+ ['Binding site', 'site'],
140
+ ['Region', 'domain'],
141
+ ['protein_match', 'domain'],
142
+ ['Domain', 'domain'],
143
+ ['siteless', 'domain'],
144
+ [undefined, 'domain'],
145
+ ])('annotationKind(%s) is %s', (featureType, kind) => {
146
+ expect(annotationKind(featureType)).toBe(kind);
147
+ });
@@ -1,17 +1,18 @@
1
- import type { ConnectedDomain } from './connectedDomains';
1
+ import type { ConnectedDomains } from './connectedDomains';
2
2
  export declare function domainTrackId(viewId: string): string;
3
3
  /**
4
4
  * A session FeatureTrack drawing `domains` on the genome: one feature per
5
5
  * domain, one subfeature per region, so a domain spanning exons draws as boxes
6
- * joined across the intron. Each feature paints the `color` it carries, and
7
- * the key names each color with its domain.
6
+ * joined across the intron. The display's color is the domains' color object,
7
+ * a categorical scale over each feature's `accession`, so its key names each
8
+ * color with its domain.
8
9
  */
9
10
  export declare function domainTrackConf({ viewId, assemblyName, name, strand, domains, }: {
10
11
  viewId: string;
11
12
  assemblyName: string;
12
13
  name: string;
13
14
  strand?: number;
14
- domains: ConnectedDomain[];
15
+ domains: ConnectedDomains;
15
16
  }): {
16
17
  type: string;
17
18
  trackId: string;
@@ -23,7 +24,6 @@ export declare function domainTrackConf({ viewId, assemblyName, name, strand, do
23
24
  type: string;
24
25
  name: string;
25
26
  accession: string;
26
- color: string;
27
27
  subfeatures: ({
28
28
  type: string;
29
29
  strand?: undefined;
@@ -48,7 +48,6 @@ export declare function domainTrackConf({ viewId, assemblyName, name, strand, do
48
48
  type: string;
49
49
  name: string;
50
50
  accession: string;
51
- color: string;
52
51
  subfeatures: ({
53
52
  type: string;
54
53
  strand?: undefined;
@@ -75,11 +74,11 @@ export declare function domainTrackConf({ viewId, assemblyName, name, strand, do
75
74
  type: string;
76
75
  displayId: string;
77
76
  color: {
78
- field: string;
77
+ title: string;
78
+ field: "accession";
79
79
  domain: string[];
80
80
  range: string[];
81
81
  labels: string[];
82
- title: string;
83
82
  };
84
83
  }[];
85
84
  };
@@ -4,18 +4,12 @@ export function domainTrackId(viewId) {
4
4
  /**
5
5
  * A session FeatureTrack drawing `domains` on the genome: one feature per
6
6
  * domain, one subfeature per region, so a domain spanning exons draws as boxes
7
- * joined across the intron. Each feature paints the `color` it carries, and
8
- * the key names each color with its domain.
7
+ * joined across the intron. The display's color is the domains' color object,
8
+ * a categorical scale over each feature's `accession`, so its key names each
9
+ * color with its domain.
9
10
  */
10
11
  export function domainTrackConf({ viewId, assemblyName, name, strand, domains, }) {
11
12
  const trackId = domainTrackId(viewId);
12
- const labelOf = new Map();
13
- for (const { color, name } of domains) {
14
- if (!labelOf.has(color)) {
15
- labelOf.set(color, name);
16
- }
17
- }
18
- const colors = [...labelOf.keys()];
19
13
  const stranded = strand === undefined ? {} : { strand };
20
14
  return {
21
15
  type: 'FeatureTrack',
@@ -24,7 +18,7 @@ export function domainTrackConf({ viewId, assemblyName, name, strand, domains, }
24
18
  assemblyNames: [assemblyName],
25
19
  adapter: {
26
20
  type: 'FromConfigAdapter',
27
- features: domains.map(({ name, accession, color, regions }, i) => {
21
+ features: domains.features.map(({ name, accession, regions }, i) => {
28
22
  const uniqueId = `${trackId}-${i}`;
29
23
  return {
30
24
  uniqueId,
@@ -35,7 +29,6 @@ export function domainTrackConf({ viewId, assemblyName, name, strand, domains, }
35
29
  type: 'protein_domain',
36
30
  name,
37
31
  accession,
38
- color,
39
32
  subfeatures: regions.map((r, j) => ({
40
33
  uniqueId: `${uniqueId}-${j}`,
41
34
  refName: r.refName,
@@ -51,13 +44,7 @@ export function domainTrackConf({ viewId, assemblyName, name, strand, domains, }
51
44
  {
52
45
  type: 'LinearBasicDisplay',
53
46
  displayId: `${trackId}-LinearBasicDisplay`,
54
- color: {
55
- field: 'color',
56
- domain: colors,
57
- range: colors,
58
- labels: colors.map(c => labelOf.get(c)),
59
- title: 'Domains',
60
- },
47
+ color: { ...domains.color, title: 'Domains' },
61
48
  },
62
49
  ],
63
50
  };
@@ -1,28 +1,30 @@
1
1
  import { expect, test } from 'vitest';
2
2
  import { domainTrackConf } from './domainTrack';
3
- const domains = [
4
- {
5
- name: 'Hb beta-like',
6
- accession: 'cd1',
7
- color: '#1b9e77',
8
- regions: [
9
- { refName: 'chr11', start: 100, end: 107 },
10
- { refName: 'chr11', start: 200, end: 208 },
11
- ],
12
- },
13
- {
14
- name: 'heme binding site',
15
- accession: 'cd2',
16
- color: '#d95f02',
17
- regions: [{ refName: 'chr11', start: 103, end: 107 }],
18
- },
19
- {
20
- name: 'Hb beta-like',
21
- accession: 'cd1',
22
- color: '#1b9e77',
23
- regions: [{ refName: 'chr11', start: 300, end: 330 }],
3
+ const domains = {
4
+ features: [
5
+ {
6
+ name: 'Hb beta-like',
7
+ accession: 'cd1',
8
+ kind: 'domain',
9
+ regions: [
10
+ { refName: 'chr11', start: 100, end: 107 },
11
+ { refName: 'chr11', start: 200, end: 208 },
12
+ ],
13
+ },
14
+ {
15
+ name: 'heme binding site',
16
+ accession: 'cd2',
17
+ kind: 'site',
18
+ regions: [{ refName: 'chr11', start: 103, end: 107 }],
19
+ },
20
+ ],
21
+ color: {
22
+ field: 'accession',
23
+ domain: ['cd1', 'cd2'],
24
+ range: ['#1b9e77', '#d95f02'],
25
+ labels: ['Hb beta-like', 'heme binding site'],
24
26
  },
25
- ];
27
+ };
26
28
  const conf = domainTrackConf({
27
29
  viewId: 'msa1',
28
30
  assemblyName: 'hg38',
@@ -33,24 +35,24 @@ const conf = domainTrackConf({
33
35
  test('one feature per domain, one subfeature per region, spanning them all', () => {
34
36
  const [first] = conf.adapter.features;
35
37
  expect(conf.trackId).toBe('msaview-domains-msa1');
36
- expect(conf.adapter.features).toHaveLength(3);
38
+ expect(conf.adapter.features).toHaveLength(2);
37
39
  expect(first).toMatchObject({
38
40
  refName: 'chr11',
39
41
  start: 100,
40
42
  end: 208,
41
43
  strand: -1,
42
44
  name: 'Hb beta-like',
43
- color: '#1b9e77',
45
+ accession: 'cd1',
44
46
  });
45
47
  expect(first.subfeatures.map(s => [s.start, s.end])).toEqual([
46
48
  [100, 107],
47
49
  [200, 208],
48
50
  ]);
49
51
  });
50
- test('the display paints each feature its own color and names it in the key', () => {
52
+ test('the display paints through the domains color object and names each in the key', () => {
51
53
  expect(conf.displays[0].color).toEqual({
52
- field: 'color',
53
- domain: ['#1b9e77', '#d95f02'],
54
+ field: 'accession',
55
+ domain: ['cd1', 'cd2'],
54
56
  range: ['#1b9e77', '#d95f02'],
55
57
  labels: ['Hb beta-like', 'heme binding site'],
56
58
  title: 'Domains',
@@ -11,12 +11,13 @@ export async function loadProteinDomains(self, signal) {
11
11
  throw new Error('No NCBI accessions found in alignment rows');
12
12
  }
13
13
  const byAccession = await fetchProteinDomains(rowAccessions.map(r => r.accession), signal);
14
- const annotations = rowAccessions.flatMap(({ rowName, accession }) => (byAccession.get(accession) ?? []).flatMap(({ signature, locations }) => signature.entry
14
+ const annotations = rowAccessions.flatMap(({ rowName, accession }) => (byAccession.get(accession) ?? []).flatMap(({ signature, locations, featureType }) => signature.entry
15
15
  ? locations.map(({ start, end }) => ({
16
16
  id: rowName,
17
17
  accession: signature.entry.accession,
18
18
  name: signature.entry.name,
19
19
  description: signature.entry.description,
20
+ featureType,
20
21
  start,
21
22
  end,
22
23
  }))
@@ -7,6 +7,7 @@ const MATCH = {
7
7
  entry: { accession: 'cd00093', name: 'HTH_XRE', description: 'a domain' },
8
8
  },
9
9
  locations: [{ start: 5, end: 40 }],
10
+ featureType: 'Region',
10
11
  };
11
12
  function model(treeMetadata) {
12
13
  return {
@@ -27,6 +28,7 @@ test('an accession row becomes one annotation per location, keyed by row name',
27
28
  accession: 'cd00093',
28
29
  name: 'HTH_XRE',
29
30
  description: 'a domain',
31
+ featureType: 'Region',
30
32
  start: 5,
31
33
  end: 40,
32
34
  },