jbrowse-plugin-msaview 5.5.0 → 5.6.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 (35) 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 +5 -5
  11. package/dist/MsaViewPanel/model.js +5 -5
  12. package/dist/MsaViewPanel/model.test.js +33 -21
  13. package/dist/chunks/{model-7RBDANXP.js → model-SEEQQSQO.js} +6 -6
  14. package/dist/chunks/model-SEEQQSQO.js.map +7 -0
  15. package/dist/jbrowse-plugin-msaview.esm.js +1 -1
  16. package/dist/jbrowse-plugin-msaview.esm.js.map +1 -1
  17. package/dist/utils/domainCache.js +17 -9
  18. package/dist/utils/ncbiDomains.d.ts +4 -1
  19. package/dist/utils/ncbiDomains.js +1 -0
  20. package/dist/version.d.ts +1 -1
  21. package/dist/version.js +1 -1
  22. package/package.json +1 -1
  23. package/src/MsaViewPanel/afterCreateAutoruns.ts +1 -1
  24. package/src/MsaViewPanel/connectedDomains.test.ts +67 -24
  25. package/src/MsaViewPanel/connectedDomains.ts +70 -26
  26. package/src/MsaViewPanel/domainTrack.test.ts +29 -27
  27. package/src/MsaViewPanel/domainTrack.ts +7 -20
  28. package/src/MsaViewPanel/loadProteinDomains.test.ts +2 -0
  29. package/src/MsaViewPanel/loadProteinDomains.ts +13 -11
  30. package/src/MsaViewPanel/model.test.ts +33 -21
  31. package/src/MsaViewPanel/model.ts +7 -7
  32. package/src/utils/domainCache.ts +17 -9
  33. package/src/utils/ncbiDomains.ts +5 -1
  34. package/src/version.ts +1 -1
  35. package/dist/chunks/model-7RBDANXP.js.map +0 -7
@@ -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
  },
@@ -1,7 +1,7 @@
1
1
  export type { MSAFormat } from 'msa-parsers';
2
2
  import type { BlastDatabase, MsaAlgorithm, PhmmerDatabase } from '../LaunchMsaView/components/BlastQuery/consts';
3
3
  import type { UnirefIdentity } from '../utils/unirefHomologs';
4
- import type { ConnectedDomain } from './connectedDomains';
4
+ import type { ConnectedDomains } from './connectedDomains';
5
5
  import type { MsaDataPayload } from './msaDataStore';
6
6
  import type { MafRegion, MsaViewInitState } from './types';
7
7
  import type { TranscriptRef } from './util';
@@ -983,11 +983,11 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
983
983
  readonly connectedHighlights: IRegion[];
984
984
  /**
985
985
  * #getter
986
- * cross-plugin contract: jbrowse-plugin-protein3d reads this to paint the
987
- * domains the alignment shows on the query row onto the structure, in
988
- * the alignment's colors
986
+ * cross-plugin contract: the query row's domains on the genome, with
987
+ * the color object every surface paints them through.
988
+ * jbrowse-plugin-protein3d reads it to draw them on the structure
989
989
  */
990
- readonly connectedDomains: ConnectedDomain[];
990
+ readonly connectedDomains: ConnectedDomains;
991
991
  } & {
992
992
  /**
993
993
  * #getter
@@ -216,9 +216,9 @@ export default function stateModelFactory() {
216
216
  },
217
217
  /**
218
218
  * #getter
219
- * cross-plugin contract: jbrowse-plugin-protein3d reads this to paint the
220
- * domains the alignment shows on the query row onto the structure, in
221
- * the alignment's colors
219
+ * cross-plugin contract: the query row's domains on the genome, with
220
+ * the color object every surface paints them through.
221
+ * jbrowse-plugin-protein3d reads it to draw them on the structure
222
222
  */
223
223
  get connectedDomains() {
224
224
  return connectedDomains(self);
@@ -431,7 +431,7 @@ export default function stateModelFactory() {
431
431
  const assemblyName = connectedView?.assemblyNames[0];
432
432
  if (!connectedView ||
433
433
  !assemblyName ||
434
- connectedDomains.length === 0 ||
434
+ connectedDomains.features.length === 0 ||
435
435
  !isSessionWithAddSessionTrack(session)) {
436
436
  return;
437
437
  }
@@ -535,7 +535,7 @@ export default function stateModelFactory() {
535
535
  self.setZoomToBaseLevel(!self.zoomToBaseLevel);
536
536
  },
537
537
  },
538
- ...(self.connectedView && self.connectedDomains.length > 0
538
+ ...(self.connectedView && self.connectedDomains.features.length > 0
539
539
  ? [
540
540
  {
541
541
  label: 'Show domains on the genome',