jbrowse-plugin-msaview 5.4.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 (47) hide show
  1. package/dist/MsaViewPanel/afterCreateAutoruns.d.ts +1 -0
  2. package/dist/MsaViewPanel/afterCreateAutoruns.js +8 -0
  3. package/dist/MsaViewPanel/connectedDomains.d.ts +58 -0
  4. package/dist/MsaViewPanel/connectedDomains.js +101 -0
  5. package/dist/MsaViewPanel/connectedDomains.test.d.ts +1 -0
  6. package/dist/MsaViewPanel/connectedDomains.test.js +147 -0
  7. package/dist/MsaViewPanel/domainTrack.d.ts +84 -0
  8. package/dist/MsaViewPanel/domainTrack.js +51 -0
  9. package/dist/MsaViewPanel/domainTrack.test.d.ts +1 -0
  10. package/dist/MsaViewPanel/domainTrack.test.js +69 -0
  11. package/dist/MsaViewPanel/loadProteinDomains.js +2 -1
  12. package/dist/MsaViewPanel/loadProteinDomains.test.js +2 -0
  13. package/dist/MsaViewPanel/model.d.ts +22 -2
  14. package/dist/MsaViewPanel/model.js +71 -2
  15. package/dist/MsaViewPanel/model.test.js +94 -0
  16. package/dist/MsaViewPanel/msaCoordToGenomeCoord.d.ts +11 -3
  17. package/dist/MsaViewPanel/msaCoordToGenomeCoord.js +21 -14
  18. package/dist/chunks/{LaunchMsaViewDialog-HGL6P4E4.js → LaunchMsaViewDialog-BG4MX2BW.js} +2 -2
  19. package/dist/chunks/{chunk-2XCUSA22.js → chunk-4TEPHD3L.js} +2 -2
  20. package/dist/chunks/{chunk-2XCUSA22.js.map → chunk-4TEPHD3L.js.map} +2 -2
  21. package/dist/chunks/model-SEEQQSQO.js +12 -0
  22. package/dist/chunks/model-SEEQQSQO.js.map +7 -0
  23. package/dist/jbrowse-plugin-msaview.esm.js +2 -2
  24. package/dist/jbrowse-plugin-msaview.esm.js.map +2 -2
  25. package/dist/utils/domainCache.js +24 -2
  26. package/dist/utils/ncbiDomains.d.ts +4 -1
  27. package/dist/utils/ncbiDomains.js +16 -25
  28. package/dist/version.d.ts +1 -1
  29. package/dist/version.js +1 -1
  30. package/package.json +1 -1
  31. package/src/LaunchMsaViewExtensionPoint/index.ts +5 -0
  32. package/src/MsaViewPanel/afterCreateAutoruns.ts +11 -0
  33. package/src/MsaViewPanel/connectedDomains.test.ts +196 -0
  34. package/src/MsaViewPanel/connectedDomains.ts +151 -0
  35. package/src/MsaViewPanel/domainTrack.test.ts +75 -0
  36. package/src/MsaViewPanel/domainTrack.ts +66 -0
  37. package/src/MsaViewPanel/loadProteinDomains.test.ts +2 -0
  38. package/src/MsaViewPanel/loadProteinDomains.ts +13 -11
  39. package/src/MsaViewPanel/model.test.ts +100 -0
  40. package/src/MsaViewPanel/model.ts +82 -1
  41. package/src/MsaViewPanel/msaCoordToGenomeCoord.ts +37 -25
  42. package/src/utils/domainCache.ts +27 -2
  43. package/src/utils/ncbiDomains.ts +20 -27
  44. package/src/version.ts +1 -1
  45. package/dist/chunks/model-36HCIOZJ.js +0 -12
  46. package/dist/chunks/model-36HCIOZJ.js.map +0 -7
  47. /package/dist/chunks/{LaunchMsaViewDialog-HGL6P4E4.js.map → LaunchMsaViewDialog-BG4MX2BW.js.map} +0 -0
@@ -57,3 +57,4 @@ export declare function syncGenomeHoverToMsaColumn(self: JBrowsePluginMsaViewMod
57
57
  */
58
58
  export declare function observeProteinHighlights(self: JBrowsePluginMsaViewModel): () => void;
59
59
  export declare function runCleanup(): void;
60
+ export declare function showDomainsOnGenomeIfAsked(self: JBrowsePluginMsaViewModel): void;
@@ -390,3 +390,11 @@ export function runCleanup() {
390
390
  console.error('Failed to cleanup old MSA data:', e);
391
391
  });
392
392
  }
393
+ export function showDomainsOnGenomeIfAsked(self) {
394
+ if (self.domainsOnGenome &&
395
+ self.connectedView &&
396
+ self.connectedDomains.features.length > 0) {
397
+ self.showDomainsOnGenome();
398
+ self.setDomainsOnGenome(false);
399
+ }
400
+ }
@@ -0,0 +1,58 @@
1
+ import type { GenomeRegion, SeqPosModel } from './msaCoordToGenomeCoord';
2
+ import type { Annotation } from 'react-msaview';
3
+ export type DomainKind = 'domain' | 'site';
4
+ /** One accession the alignment draws on the query row. */
5
+ export interface ConnectedDomain {
6
+ /** the label the alignment's legend shows for the accession */
7
+ name: string;
8
+ accession: 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;
14
+ /**
15
+ * 0-based half-open, ascending, contiguous bases merged: one region per exon
16
+ * piece of a domain, or per run of a site's scattered residues, in the
17
+ * transcript's refName spelling
18
+ */
19
+ regions: GenomeRegion[];
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;
39
+ export interface DomainSource extends SeqPosModel {
40
+ showDomains: boolean;
41
+ querySeqName: string;
42
+ annotationsByRow: Record<string, Annotation[]>;
43
+ annotationTypes: Map<string, Annotation>;
44
+ featureColors: Map<Annotation, {
45
+ fill: string;
46
+ }>;
47
+ }
48
+ /**
49
+ * The genome regions under the query row's residues `positions`, 0-based
50
+ * ungapped.
51
+ */
52
+ export declare function residuesToGenomeRegions(model: SeqPosModel, positions: Iterable<number>): GenomeRegion[];
53
+ /**
54
+ * The query row's domains as the alignment shows them, one per accession, so
55
+ * a CDD site's scattered residues are one feature. Empty while the domain
56
+ * overlay is off.
57
+ */
58
+ export declare function connectedDomains(model: DomainSource): ConnectedDomains;
@@ -0,0 +1,101 @@
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
+ */
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
+ }
22
+ function mergeAdjacent(regions) {
23
+ const merged = [];
24
+ for (const region of regions.toSorted((a, b) => a.start - b.start)) {
25
+ const last = merged.at(-1);
26
+ if (last && last.refName === region.refName && region.start <= last.end) {
27
+ last.end = Math.max(last.end, region.end);
28
+ }
29
+ else {
30
+ merged.push({ ...region });
31
+ }
32
+ }
33
+ return merged;
34
+ }
35
+ /**
36
+ * The genome regions under the query row's residues `positions`, 0-based
37
+ * ungapped.
38
+ */
39
+ export function residuesToGenomeRegions(model, positions) {
40
+ const pieces = [];
41
+ for (const pos of positions) {
42
+ pieces.push(...seqPosToGenomeRegions(model, pos));
43
+ }
44
+ return mergeAdjacent(pieces);
45
+ }
46
+ /**
47
+ * The query row's domains as the alignment shows them, one per accession, so
48
+ * a CDD site's scattered residues are one feature. Empty while the domain
49
+ * overlay is off.
50
+ */
51
+ export function connectedDomains(model) {
52
+ const byAccession = new Map();
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
+ }
62
+ }
63
+ }
64
+ const placed = [...byAccession]
65
+ .flatMap(([accession, annotations]) => {
66
+ // annotations count residues 1-based inclusive
67
+ const positions = new Set();
68
+ for (const { start, end } of annotations) {
69
+ for (let pos = start - 1; pos < end; pos++) {
70
+ positions.add(pos);
71
+ }
72
+ }
73
+ const first = annotations[0];
74
+ const fill = model.featureColors.get(first)?.fill;
75
+ const regions = residuesToGenomeRegions(model, positions);
76
+ return fill && regions.length > 0
77
+ ? [
78
+ {
79
+ feature: {
80
+ name: model.annotationTypes.get(accession)?.name ?? first.name,
81
+ accession,
82
+ kind: annotationKind(first.featureType),
83
+ regions,
84
+ },
85
+ fill,
86
+ residues: positions.size,
87
+ },
88
+ ]
89
+ : [];
90
+ })
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
+ };
101
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,147 @@
1
+ import { genomeToTranscriptSeqMapping } from 'g2p_mapper';
2
+ import { describe, expect, test } from 'vitest';
3
+ import { annotationKind, connectedDomains } from './connectedDomains';
4
+ // five codons read from 207 down: 207-205, 204-202, 201-200 then 106 across
5
+ // the intron, 105-103, 102-100
6
+ const reverseTranscript = genomeToTranscriptSeqMapping({
7
+ refName: 'chr1',
8
+ strand: -1,
9
+ start: 100,
10
+ end: 208,
11
+ subfeatures: [
12
+ { refName: 'chr1', type: 'CDS', start: 100, end: 107, phase: 0 },
13
+ { refName: 'chr1', type: 'CDS', start: 200, end: 208, phase: 0 },
14
+ ],
15
+ });
16
+ function annotation(accession, start, end, id = 'QUERY') {
17
+ return {
18
+ id,
19
+ accession,
20
+ name: `name ${accession}`,
21
+ description: '',
22
+ start,
23
+ end,
24
+ };
25
+ }
26
+ function source(annotations, overrides = {}) {
27
+ return {
28
+ showDomains: true,
29
+ querySeqName: 'QUERY',
30
+ transcriptToMsaMap: reverseTranscript,
31
+ annotationsByRow: Object.groupBy(annotations, a => a.id),
32
+ annotationTypes: new Map(annotations.map(a => [
33
+ a.accession,
34
+ { ...a, name: `legend ${a.accession}` },
35
+ ])),
36
+ featureColors: new Map(annotations.map(a => [a, { fill: `fill-${a.accession}` }])),
37
+ ...overrides,
38
+ };
39
+ }
40
+ describe('connectedDomains on a reverse-strand transcript', () => {
41
+ test('a domain crossing the exon boundary is one region per exon piece', () => {
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'],
59
+ },
60
+ });
61
+ });
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([
68
+ { refName: 'chr1', start: 100, end: 107 },
69
+ { refName: 'chr1', start: 200, end: 208 },
70
+ ]);
71
+ });
72
+ test('a scattered site is one entry, its residues merged where they touch', () => {
73
+ const { features } = connectedDomains(source([
74
+ annotation('small', 4, 5),
75
+ annotation('heme', 1, 1),
76
+ annotation('heme', 3, 3),
77
+ annotation('heme', 4, 4),
78
+ ]));
79
+ expect(features.map(d => d.accession)).toEqual(['heme', 'small']);
80
+ expect(features[0]).toEqual({
81
+ name: 'legend heme',
82
+ accession: 'heme',
83
+ kind: 'domain',
84
+ regions: [
85
+ { refName: 'chr1', start: 103, end: 107 },
86
+ { refName: 'chr1', start: 200, end: 202 },
87
+ { refName: 'chr1', start: 205, end: 208 },
88
+ ],
89
+ });
90
+ });
91
+ test('a trimmed query row shifts by querySeqOffset', () => {
92
+ const [domain] = connectedDomains(source([annotation('site', 1, 2)], { querySeqOffset: 1 })).features;
93
+ expect(domain.regions).toEqual([
94
+ { refName: 'chr1', start: 106, end: 107 },
95
+ { refName: 'chr1', start: 200, end: 205 },
96
+ ]);
97
+ });
98
+ test('residues past the transcript drop out, and a domain with none left', () => {
99
+ expect(connectedDomains(source([annotation('tail', 5, 9), annotation('off', 7, 9)])).features).toEqual([
100
+ expect.objectContaining({
101
+ accession: 'tail',
102
+ regions: [{ refName: 'chr1', start: 100, end: 103 }],
103
+ }),
104
+ ]);
105
+ });
106
+ test('only the query row, and nothing while the overlay is off', () => {
107
+ const annotations = [annotation('other', 1, 5, 'Mouse')];
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
+ });
113
+ });
114
+ });
115
+ test('a MAF row maps one base per residue', () => {
116
+ const [domain] = connectedDomains(source([annotation('d', 3, 6)], {
117
+ transcriptToMsaMap: undefined,
118
+ mafRegion: { refName: 'chr2', start: 50, end: 100, assemblyName: 'hg38' },
119
+ })).features;
120
+ expect(domain.regions).toEqual([{ refName: 'chr2', start: 52, end: 56 }]);
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
+ });
@@ -0,0 +1,84 @@
1
+ import type { ConnectedDomains } from './connectedDomains';
2
+ export declare function domainTrackId(viewId: string): string;
3
+ /**
4
+ * A session FeatureTrack drawing `domains` on the genome: one feature per
5
+ * domain, one subfeature per region, so a domain spanning exons draws as boxes
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.
9
+ */
10
+ export declare function domainTrackConf({ viewId, assemblyName, name, strand, domains, }: {
11
+ viewId: string;
12
+ assemblyName: string;
13
+ name: string;
14
+ strand?: number;
15
+ domains: ConnectedDomains;
16
+ }): {
17
+ type: string;
18
+ trackId: string;
19
+ name: string;
20
+ assemblyNames: string[];
21
+ adapter: {
22
+ type: string;
23
+ features: ({
24
+ type: string;
25
+ name: string;
26
+ accession: string;
27
+ subfeatures: ({
28
+ type: string;
29
+ strand?: undefined;
30
+ uniqueId: string;
31
+ refName: string;
32
+ start: number;
33
+ end: number;
34
+ } | {
35
+ type: string;
36
+ strand: number;
37
+ uniqueId: string;
38
+ refName: string;
39
+ start: number;
40
+ end: number;
41
+ })[];
42
+ strand?: undefined;
43
+ uniqueId: string;
44
+ refName: string;
45
+ start: number;
46
+ end: number;
47
+ } | {
48
+ type: string;
49
+ name: string;
50
+ accession: string;
51
+ subfeatures: ({
52
+ type: string;
53
+ strand?: undefined;
54
+ uniqueId: string;
55
+ refName: string;
56
+ start: number;
57
+ end: number;
58
+ } | {
59
+ type: string;
60
+ strand: number;
61
+ uniqueId: string;
62
+ refName: string;
63
+ start: number;
64
+ end: number;
65
+ })[];
66
+ strand: number;
67
+ uniqueId: string;
68
+ refName: string;
69
+ start: number;
70
+ end: number;
71
+ })[];
72
+ };
73
+ displays: {
74
+ type: string;
75
+ displayId: string;
76
+ color: {
77
+ title: string;
78
+ field: "accession";
79
+ domain: string[];
80
+ range: string[];
81
+ labels: string[];
82
+ };
83
+ }[];
84
+ };
@@ -0,0 +1,51 @@
1
+ export function domainTrackId(viewId) {
2
+ return `msaview-domains-${viewId}`;
3
+ }
4
+ /**
5
+ * A session FeatureTrack drawing `domains` on the genome: one feature per
6
+ * domain, one subfeature per region, so a domain spanning exons draws as boxes
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.
10
+ */
11
+ export function domainTrackConf({ viewId, assemblyName, name, strand, domains, }) {
12
+ const trackId = domainTrackId(viewId);
13
+ const stranded = strand === undefined ? {} : { strand };
14
+ return {
15
+ type: 'FeatureTrack',
16
+ trackId,
17
+ name,
18
+ assemblyNames: [assemblyName],
19
+ adapter: {
20
+ type: 'FromConfigAdapter',
21
+ features: domains.features.map(({ name, accession, regions }, i) => {
22
+ const uniqueId = `${trackId}-${i}`;
23
+ return {
24
+ uniqueId,
25
+ refName: regions[0].refName,
26
+ start: Math.min(...regions.map(r => r.start)),
27
+ end: Math.max(...regions.map(r => r.end)),
28
+ ...stranded,
29
+ type: 'protein_domain',
30
+ name,
31
+ accession,
32
+ subfeatures: regions.map((r, j) => ({
33
+ uniqueId: `${uniqueId}-${j}`,
34
+ refName: r.refName,
35
+ start: r.start,
36
+ end: r.end,
37
+ ...stranded,
38
+ type: 'protein_domain_part',
39
+ })),
40
+ };
41
+ }),
42
+ },
43
+ displays: [
44
+ {
45
+ type: 'LinearBasicDisplay',
46
+ displayId: `${trackId}-LinearBasicDisplay`,
47
+ color: { ...domains.color, title: 'Domains' },
48
+ },
49
+ ],
50
+ };
51
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,69 @@
1
+ import { expect, test } from 'vitest';
2
+ import { domainTrackConf } from './domainTrack';
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'],
26
+ },
27
+ };
28
+ const conf = domainTrackConf({
29
+ viewId: 'msa1',
30
+ assemblyName: 'hg38',
31
+ name: 'HBB domains (from the alignment)',
32
+ strand: -1,
33
+ domains,
34
+ });
35
+ test('one feature per domain, one subfeature per region, spanning them all', () => {
36
+ const [first] = conf.adapter.features;
37
+ expect(conf.trackId).toBe('msaview-domains-msa1');
38
+ expect(conf.adapter.features).toHaveLength(2);
39
+ expect(first).toMatchObject({
40
+ refName: 'chr11',
41
+ start: 100,
42
+ end: 208,
43
+ strand: -1,
44
+ name: 'Hb beta-like',
45
+ accession: 'cd1',
46
+ });
47
+ expect(first.subfeatures.map(s => [s.start, s.end])).toEqual([
48
+ [100, 107],
49
+ [200, 208],
50
+ ]);
51
+ });
52
+ test('the display paints through the domains color object and names each in the key', () => {
53
+ expect(conf.displays[0].color).toEqual({
54
+ field: 'accession',
55
+ domain: ['cd1', 'cd2'],
56
+ range: ['#1b9e77', '#d95f02'],
57
+ labels: ['Hb beta-like', 'heme binding site'],
58
+ title: 'Domains',
59
+ });
60
+ });
61
+ test('a MAF view knows no strand, and the features carry none', () => {
62
+ const unstranded = domainTrackConf({
63
+ viewId: 'msa1',
64
+ assemblyName: 'hg38',
65
+ name: 'x',
66
+ domains,
67
+ });
68
+ expect('strand' in unstranded.adapter.features[0]).toBe(false);
69
+ });
@@ -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,6 +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 { ConnectedDomains } from './connectedDomains';
4
5
  import type { MsaDataPayload } from './msaDataStore';
5
6
  import type { MafRegion, MsaViewInitState } from './types';
6
7
  import type { TranscriptRef } from './util';
@@ -497,7 +498,7 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
497
498
  clades: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IType<import("react-msaview").Clade, import("react-msaview").Clade, import("react-msaview").Clade>>, [undefined]>;
498
499
  encodings: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IType<import("react-msaview").Encoding, import("react-msaview").Encoding, import("react-msaview").Encoding>>, [undefined]>;
499
500
  rowPanels: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IType<import("react-msaview").RowPanelSpec, import("react-msaview").RowPanelSpec, import("react-msaview").RowPanelSpec>>, [undefined]>;
500
- }, "init" | "querySeqName" | "launchCompleted" | "embeddedMode" | "connectedViewId" | "connectedFeature" | "connectedTranscript" | "blastParams" | "orthologParams" | "querySeqOffset" | "uniprotId" | "zoomToBaseLevel" | "dataStoreId" | "mafRegion"> & {
501
+ }, "init" | "querySeqName" | "launchCompleted" | "embeddedMode" | "connectedViewId" | "connectedFeature" | "connectedTranscript" | "blastParams" | "orthologParams" | "querySeqOffset" | "uniprotId" | "zoomToBaseLevel" | "dataStoreId" | "mafRegion" | "domainsOnGenome"> & {
501
502
  connectedViewId: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
502
503
  connectedFeature: import("@jbrowse/mobx-state-tree").IType<any, any, any>;
503
504
  connectedTranscript: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
@@ -512,6 +513,7 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
512
513
  launchCompleted: import("@jbrowse/mobx-state-tree").IType<boolean | undefined, boolean, boolean>;
513
514
  mafRegion: import("@jbrowse/mobx-state-tree").IType<MafRegion | undefined, MafRegion | undefined, MafRegion | undefined>;
514
515
  embeddedMode: import("@jbrowse/mobx-state-tree").IType<boolean | undefined, boolean, boolean>;
516
+ domainsOnGenome: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
515
517
  }, {
516
518
  width: number;
517
519
  bodyMounted: boolean;
@@ -979,6 +981,13 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
979
981
  * to draw the same highlights in its own display
980
982
  */
981
983
  readonly connectedHighlights: IRegion[];
984
+ /**
985
+ * #getter
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
+ */
990
+ readonly connectedDomains: ConnectedDomains;
982
991
  } & {
983
992
  /**
984
993
  * #getter
@@ -1089,6 +1098,16 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
1089
1098
  * #action
1090
1099
  */
1091
1100
  setDomainsRequested(arg: boolean): void;
1101
+ /**
1102
+ * #action
1103
+ */
1104
+ setDomainsOnGenome(arg: boolean): void;
1105
+ /**
1106
+ * #action
1107
+ * Add the query row's domains to the connected genome view as a session
1108
+ * track, replacing the one an earlier call added.
1109
+ */
1110
+ showDomainsOnGenome(): void;
1092
1111
  /**
1093
1112
  * #action
1094
1113
  */
@@ -1510,7 +1529,7 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
1510
1529
  clades: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IType<import("react-msaview").Clade, import("react-msaview").Clade, import("react-msaview").Clade>>, [undefined]>;
1511
1530
  encodings: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IType<import("react-msaview").Encoding, import("react-msaview").Encoding, import("react-msaview").Encoding>>, [undefined]>;
1512
1531
  rowPanels: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IType<import("react-msaview").RowPanelSpec, import("react-msaview").RowPanelSpec, import("react-msaview").RowPanelSpec>>, [undefined]>;
1513
- }, "init" | "querySeqName" | "launchCompleted" | "embeddedMode" | "connectedViewId" | "connectedFeature" | "connectedTranscript" | "blastParams" | "orthologParams" | "querySeqOffset" | "uniprotId" | "zoomToBaseLevel" | "dataStoreId" | "mafRegion"> & {
1532
+ }, "init" | "querySeqName" | "launchCompleted" | "embeddedMode" | "connectedViewId" | "connectedFeature" | "connectedTranscript" | "blastParams" | "orthologParams" | "querySeqOffset" | "uniprotId" | "zoomToBaseLevel" | "dataStoreId" | "mafRegion" | "domainsOnGenome"> & {
1514
1533
  connectedViewId: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
1515
1534
  connectedFeature: import("@jbrowse/mobx-state-tree").IType<any, any, any>;
1516
1535
  connectedTranscript: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
@@ -1525,6 +1544,7 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
1525
1544
  launchCompleted: import("@jbrowse/mobx-state-tree").IType<boolean | undefined, boolean, boolean>;
1526
1545
  mafRegion: import("@jbrowse/mobx-state-tree").IType<MafRegion | undefined, MafRegion | undefined, MafRegion | undefined>;
1527
1546
  embeddedMode: import("@jbrowse/mobx-state-tree").IType<boolean | undefined, boolean, boolean>;
1547
+ domainsOnGenome: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
1528
1548
  }>, "id" | "type" | "drawLabels" | "drawNodeBubbles" | "drawNodeLabels" | "drawTree" | "labelsAlignRight" | "overviewHeight" | "showBranchLen" | "showTreeOverview" | "treeAreaWidth" | "treeOrder" | "treeWidth" | "bgColor" | "colorSchemeName" | "customColorScheme" | "msaFormat" | "showColumnStats" | "allowedGappyness" | "clades" | "colWidth" | "collapsed" | "columnTracks" | "currentAlignment" | "data" | "drawMsaLetters" | "encodings" | "features" | "gffFilehandle" | "height" | "hideGaps" | "highlightColumns" | "highlights" | "msaFilehandle" | "region" | "relativeTo" | "residueMappings" | "rotated" | "rowHeight" | "rowPanels" | "scrollX" | "scrollY" | "scrollZoom" | "scrollZoomAxis" | "selection" | "showDomainLegend" | "showDomains" | "showOnly" | "subFeatureRows" | "trackHeights" | "treeFilehandle" | "treeMetadataFilehandle" | "treeRoot" | "turnedOffFeatures" | "turnedOffTracks"> & ({
1529
1549
  bgColor: boolean;
1530
1550
  colorSchemeName: string;