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.
- package/dist/MsaViewPanel/afterCreateAutoruns.js +1 -1
- package/dist/MsaViewPanel/connectedDomains.d.ts +29 -11
- package/dist/MsaViewPanel/connectedDomains.js +47 -20
- package/dist/MsaViewPanel/connectedDomains.test.js +63 -24
- package/dist/MsaViewPanel/domainTrack.d.ts +7 -8
- package/dist/MsaViewPanel/domainTrack.js +5 -18
- package/dist/MsaViewPanel/domainTrack.test.js +29 -27
- package/dist/MsaViewPanel/loadProteinDomains.js +2 -1
- package/dist/MsaViewPanel/loadProteinDomains.test.js +2 -0
- package/dist/MsaViewPanel/model.d.ts +5 -5
- package/dist/MsaViewPanel/model.js +5 -5
- package/dist/MsaViewPanel/model.test.js +33 -21
- package/dist/chunks/{model-7RBDANXP.js → model-SEEQQSQO.js} +6 -6
- package/dist/chunks/model-SEEQQSQO.js.map +7 -0
- package/dist/jbrowse-plugin-msaview.esm.js +1 -1
- package/dist/jbrowse-plugin-msaview.esm.js.map +1 -1
- package/dist/utils/domainCache.js +17 -9
- package/dist/utils/ncbiDomains.d.ts +4 -1
- package/dist/utils/ncbiDomains.js +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/MsaViewPanel/afterCreateAutoruns.ts +1 -1
- package/src/MsaViewPanel/connectedDomains.test.ts +67 -24
- package/src/MsaViewPanel/connectedDomains.ts +70 -26
- package/src/MsaViewPanel/domainTrack.test.ts +29 -27
- package/src/MsaViewPanel/domainTrack.ts +7 -20
- package/src/MsaViewPanel/loadProteinDomains.test.ts +2 -0
- package/src/MsaViewPanel/loadProteinDomains.ts +13 -11
- package/src/MsaViewPanel/model.test.ts +33 -21
- package/src/MsaViewPanel/model.ts +7 -7
- package/src/utils/domainCache.ts +17 -9
- package/src/utils/ncbiDomains.ts +5 -1
- package/src/version.ts +1 -1
- 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
|
-
|
|
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
|
|
6
|
+
/** the label the alignment's legend shows for the accession */
|
|
9
7
|
name: string;
|
|
10
8
|
accession: string;
|
|
11
|
-
/**
|
|
12
|
-
|
|
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
|
|
37
|
-
*
|
|
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):
|
|
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
|
|
29
|
-
*
|
|
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
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
list
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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
|
|
74
|
+
const fill = model.featureColors.get(first)?.fill;
|
|
57
75
|
const regions = residuesToGenomeRegions(model, positions);
|
|
58
|
-
return
|
|
76
|
+
return fill && regions.length > 0
|
|
59
77
|
? [
|
|
60
78
|
{
|
|
61
|
-
|
|
79
|
+
feature: {
|
|
62
80
|
name: model.annotationTypes.get(accession)?.name ?? first.name,
|
|
63
81
|
accession,
|
|
64
|
-
|
|
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
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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,
|
|
55
|
-
const
|
|
56
|
-
expect(
|
|
57
|
-
expect(
|
|
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
|
|
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(
|
|
70
|
-
expect(
|
|
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
|
-
|
|
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 {
|
|
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.
|
|
7
|
-
*
|
|
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:
|
|
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
|
-
|
|
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.
|
|
8
|
-
*
|
|
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,
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
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: '
|
|
53
|
-
domain: ['
|
|
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 {
|
|
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:
|
|
987
|
-
*
|
|
988
|
-
* the
|
|
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:
|
|
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:
|
|
220
|
-
*
|
|
221
|
-
* the
|
|
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',
|