jbrowse-plugin-msaview 2.7.2 → 2.7.3
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/AddHighlightModel/MsaToGenomeHighlight.js +6 -5
- package/dist/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.js +1 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/useCachedBlastResults.d.ts +1 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/useCachedBlastResults.js +5 -2
- package/dist/MsaViewPanel/doLaunchBlast.js +10 -22
- package/dist/MsaViewPanel/genomeToMSA.js +13 -6
- package/dist/MsaViewPanel/genomeToMSA.test.js +35 -6
- package/dist/MsaViewPanel/model.d.ts +68 -66
- package/dist/MsaViewPanel/model.js +14 -12
- package/dist/MsaViewPanel/msaCoordToGenomeCoord.d.ts +32 -13
- package/dist/MsaViewPanel/msaCoordToGenomeCoord.js +43 -14
- package/dist/MsaViewPanel/msaCoordToGenomeCoord.test.js +108 -18
- package/dist/MsaViewPanel/msaDataStore.js +8 -17
- package/dist/MsaViewPanel/syncGenomeHoverToMsaColumn.test.js +8 -6
- package/dist/jbrowse-plugin-msaview.umd.production.min.js +31 -31
- package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
- package/dist/utils/blastCache.d.ts +1 -2
- package/dist/utils/blastCache.js +9 -22
- package/dist/utils/domainCache.js +6 -15
- package/dist/utils/idb.d.ts +12 -0
- package/dist/utils/idb.js +21 -0
- package/dist/utils/taxonomyNames.js +13 -18
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/src/AddHighlightModel/MsaToGenomeHighlight.tsx +6 -8
- package/src/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.tsx +1 -1
- package/src/LaunchMsaView/components/NCBIBlastQuery/useCachedBlastResults.ts +4 -2
- package/src/MsaViewPanel/doLaunchBlast.ts +20 -29
- package/src/MsaViewPanel/genomeToMSA.test.ts +38 -6
- package/src/MsaViewPanel/genomeToMSA.ts +14 -6
- package/src/MsaViewPanel/model.ts +17 -12
- package/src/MsaViewPanel/msaCoordToGenomeCoord.test.ts +117 -18
- package/src/MsaViewPanel/msaCoordToGenomeCoord.ts +70 -26
- package/src/MsaViewPanel/msaDataStore.ts +17 -17
- package/src/MsaViewPanel/syncGenomeHoverToMsaColumn.test.ts +8 -6
- package/src/utils/blastCache.ts +20 -23
- package/src/utils/domainCache.ts +14 -18
- package/src/utils/idb.ts +28 -0
- package/src/utils/taxonomyNames.ts +22 -24
- package/src/version.ts +1 -1
- package/dist/MsaViewPanel/blosum62.d.ts +0 -2
- package/dist/MsaViewPanel/blosum62.js +0 -627
- package/src/MsaViewPanel/blosum62.ts +0 -628
|
@@ -29,6 +29,5 @@ export declare function saveBlastResult({ proteinSequence, blastDatabase, blastP
|
|
|
29
29
|
transcriptName?: string;
|
|
30
30
|
geneName?: string;
|
|
31
31
|
}): Promise<CachedBlastResult>;
|
|
32
|
-
export declare function getAllCachedResults(): Promise<
|
|
32
|
+
export declare function getAllCachedResults(): Promise<CachedBlastResult[]>;
|
|
33
33
|
export declare function deleteCachedResult(id: string): Promise<void>;
|
|
34
|
-
export declare function clearAllCachedResults(): Promise<void>;
|
package/dist/utils/blastCache.js
CHANGED
|
@@ -1,24 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createDbOpener } from './idb';
|
|
2
2
|
const DB_NAME = 'jbrowse-msaview-blast-cache';
|
|
3
3
|
const STORE_NAME = 'blast-results';
|
|
4
4
|
const DB_VERSION = 2;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
db.createObjectStore(STORE_NAME, { keyPath: 'id' });
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
}).catch((e) => {
|
|
17
|
-
dbPromise = undefined;
|
|
18
|
-
throw e;
|
|
19
|
-
});
|
|
20
|
-
return dbPromise;
|
|
21
|
-
}
|
|
5
|
+
const getDB = createDbOpener(DB_NAME, DB_VERSION, (db, oldVersion) => {
|
|
6
|
+
if (oldVersion < 2 && db.objectStoreNames.contains(STORE_NAME)) {
|
|
7
|
+
db.deleteObjectStore(STORE_NAME);
|
|
8
|
+
}
|
|
9
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
10
|
+
db.createObjectStore(STORE_NAME, { keyPath: 'id' });
|
|
11
|
+
}
|
|
12
|
+
});
|
|
22
13
|
function createCacheKey(proteinSequence, blastDatabase, blastProgram, msaAlgorithm, transcriptId) {
|
|
23
14
|
const idPart = transcriptId ? `:${transcriptId}` : '';
|
|
24
15
|
// msaAlgorithm is part of the key because the stored msa/tree are produced by
|
|
@@ -57,7 +48,3 @@ export async function deleteCachedResult(id) {
|
|
|
57
48
|
const db = await getDB();
|
|
58
49
|
await db.delete(STORE_NAME, id);
|
|
59
50
|
}
|
|
60
|
-
export async function clearAllCachedResults() {
|
|
61
|
-
const db = await getDB();
|
|
62
|
-
await db.clear(STORE_NAME);
|
|
63
|
-
}
|
|
@@ -1,21 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createDbOpener } from './idb';
|
|
2
2
|
const DB_NAME = 'jbrowse-msaview-domain-cache';
|
|
3
3
|
const STORE_NAME = 'domains';
|
|
4
4
|
const DB_VERSION = 1;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
db.createObjectStore(STORE_NAME, { keyPath: 'accession' });
|
|
11
|
-
}
|
|
12
|
-
},
|
|
13
|
-
}).catch((e) => {
|
|
14
|
-
dbPromise = undefined;
|
|
15
|
-
throw e;
|
|
16
|
-
});
|
|
17
|
-
return dbPromise;
|
|
18
|
-
}
|
|
5
|
+
const getDB = createDbOpener(DB_NAME, DB_VERSION, db => {
|
|
6
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
7
|
+
db.createObjectStore(STORE_NAME, { keyPath: 'accession' });
|
|
8
|
+
}
|
|
9
|
+
});
|
|
19
10
|
export async function getCachedDomains(accessions) {
|
|
20
11
|
const db = await getDB();
|
|
21
12
|
const tx = db.transaction(STORE_NAME, 'readonly');
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { DBSchema, IDBPDatabase, OpenDBCallbacks } from 'idb';
|
|
2
|
+
/**
|
|
3
|
+
* Memoized `openDB` for a typed schema, shared by this plugin's caches.
|
|
4
|
+
*
|
|
5
|
+
* The connection promise is cached so callers share one connection, and dropped
|
|
6
|
+
* again if the open fails, so a later call retries instead of replaying the same
|
|
7
|
+
* rejection forever (IndexedDB is unavailable in some private-browsing modes).
|
|
8
|
+
*
|
|
9
|
+
* Passing a DBSchema is what keeps `get`/`getAll` from returning `any`: with an
|
|
10
|
+
* untyped database every cached record reaches the UI unchecked.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createDbOpener<T extends DBSchema>(name: string, version: number, upgrade: OpenDBCallbacks<T>['upgrade']): () => Promise<IDBPDatabase<T>>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { openDB } from 'idb';
|
|
2
|
+
/**
|
|
3
|
+
* Memoized `openDB` for a typed schema, shared by this plugin's caches.
|
|
4
|
+
*
|
|
5
|
+
* The connection promise is cached so callers share one connection, and dropped
|
|
6
|
+
* again if the open fails, so a later call retries instead of replaying the same
|
|
7
|
+
* rejection forever (IndexedDB is unavailable in some private-browsing modes).
|
|
8
|
+
*
|
|
9
|
+
* Passing a DBSchema is what keeps `get`/`getAll` from returning `any`: with an
|
|
10
|
+
* untyped database every cached record reaches the UI unchecked.
|
|
11
|
+
*/
|
|
12
|
+
export function createDbOpener(name, version, upgrade) {
|
|
13
|
+
let dbPromise;
|
|
14
|
+
return () => {
|
|
15
|
+
dbPromise ??= openDB(name, version, { upgrade }).catch((e) => {
|
|
16
|
+
dbPromise = undefined;
|
|
17
|
+
throw e;
|
|
18
|
+
});
|
|
19
|
+
return dbPromise;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -1,23 +1,15 @@
|
|
|
1
|
-
import { openDB } from 'idb';
|
|
2
1
|
import { efetchUrl } from './eutils';
|
|
2
|
+
import { textfetch } from './fetch';
|
|
3
|
+
import { createDbOpener } from './idb';
|
|
3
4
|
const DB_NAME = 'jbrowse-msaview-taxonomy-cache';
|
|
4
5
|
const STORE_NAME = 'common-names';
|
|
5
6
|
const DB_VERSION = 2;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
db.createObjectStore(STORE_NAME, { keyPath: 'taxid' });
|
|
14
|
-
},
|
|
15
|
-
}).catch((e) => {
|
|
16
|
-
dbPromise = undefined;
|
|
17
|
-
throw e;
|
|
18
|
-
});
|
|
19
|
-
return dbPromise;
|
|
20
|
-
}
|
|
7
|
+
const getDB = createDbOpener(DB_NAME, DB_VERSION, db => {
|
|
8
|
+
if (db.objectStoreNames.contains(STORE_NAME)) {
|
|
9
|
+
db.deleteObjectStore(STORE_NAME);
|
|
10
|
+
}
|
|
11
|
+
db.createObjectStore(STORE_NAME, { keyPath: 'taxid' });
|
|
12
|
+
});
|
|
21
13
|
async function getCachedTaxonomies(taxids) {
|
|
22
14
|
const db = await getDB();
|
|
23
15
|
const tx = db.transaction(STORE_NAME, 'readonly');
|
|
@@ -59,8 +51,11 @@ export async function fetchTaxonomyInfo(taxids) {
|
|
|
59
51
|
const batch = uncachedTaxids.slice(i, i + batchSize);
|
|
60
52
|
const idsParam = batch.join(',');
|
|
61
53
|
try {
|
|
62
|
-
|
|
63
|
-
|
|
54
|
+
// textfetch rather than a bare fetch: an NCBI 429/5xx returns an HTML
|
|
55
|
+
// error body that the regexes below silently find nothing in, so without
|
|
56
|
+
// the status check a throttled batch looks like "these taxa have no
|
|
57
|
+
// names" instead of reporting why
|
|
58
|
+
const text = await textfetch(efetchUrl({ db: 'taxonomy', id: idsParam, retmode: 'xml' }));
|
|
64
59
|
// Build a map of taxid -> taxon block by finding Taxon elements.
|
|
65
60
|
// Prefer entries with <LineageEx> (full top-level entries) over nested
|
|
66
61
|
// entries inside another taxon's LineageEx
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "2.7.
|
|
1
|
+
export declare const version = "2.7.3";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '2.7.
|
|
1
|
+
export const version = '2.7.3';
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "2.7.
|
|
2
|
+
"version": "2.7.3",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"name": "jbrowse-plugin-msaview",
|
|
5
5
|
"repository": {
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"puppeteer": "^25.3.0",
|
|
52
52
|
"react": "^19.2.8",
|
|
53
53
|
"react-dom": "^19.2.8",
|
|
54
|
-
"react-msaview": "^5.
|
|
54
|
+
"react-msaview": "^5.7.0",
|
|
55
55
|
"rimraf": "^6.1.3",
|
|
56
56
|
"rxjs": "^7.8.2",
|
|
57
57
|
"serve": "^14.2.6",
|
|
@@ -23,14 +23,12 @@ const MsaToGenomeHighlight = observer(function MsaToGenomeHighlight2({
|
|
|
23
23
|
// The persistent click selection always shows. The hover codon is suppressed
|
|
24
24
|
// while hovering the LGV — GenomeMouseoverHighlight handles the single-bp
|
|
25
25
|
// display in that case, so we don't stack a wider codon band on top of it.
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
r !== undefined,
|
|
33
|
-
)
|
|
26
|
+
const highlights = [
|
|
27
|
+
...(msaView?.connectedClickHighlights ?? []),
|
|
28
|
+
...(hasHoverPosition(hovered)
|
|
29
|
+
? []
|
|
30
|
+
: (msaView?.connectedHoverHighlights ?? [])),
|
|
31
|
+
]
|
|
34
32
|
|
|
35
33
|
return highlights.length ? (
|
|
36
34
|
<MsaToGenomeHighlightRenderer model={model} highlights={highlights} />
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import useSWR from 'swr'
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
-
clearAllCachedResults,
|
|
5
4
|
deleteCachedResult,
|
|
6
5
|
getAllCachedResults,
|
|
7
6
|
} from '../../../utils/blastCache'
|
|
@@ -30,8 +29,11 @@ export function useCachedBlastResults(geneIds: string[]) {
|
|
|
30
29
|
)
|
|
31
30
|
}
|
|
32
31
|
|
|
32
|
+
// deletes only what this hook listed, i.e. the results for these gene ids.
|
|
33
|
+
// The list the user is looking at is gene-scoped, so a store-wide clear here
|
|
34
|
+
// would silently throw away every other gene's cached alignments too
|
|
33
35
|
const handleClearAll = async () => {
|
|
34
|
-
await
|
|
36
|
+
await Promise.all((results ?? []).map(r => deleteCachedResult(r.id)))
|
|
35
37
|
await mutate([], false)
|
|
36
38
|
}
|
|
37
39
|
|
|
@@ -25,36 +25,29 @@ export async function doLaunchBlast({
|
|
|
25
25
|
} = self.blastParams!
|
|
26
26
|
const cleanedSeq = cleanProteinSequence(proteinSequence)
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
const onProgress = (arg: string) => {
|
|
29
|
+
self.setProgress(arg)
|
|
30
|
+
}
|
|
31
|
+
|
|
30
32
|
if (existingRid) {
|
|
33
|
+
// publish it before the first poll so the view can link out to NCBI while
|
|
34
|
+
// the job is still running
|
|
31
35
|
self.setRid(existingRid)
|
|
32
|
-
const result = await queryBlastFromRid({
|
|
33
|
-
rid: existingRid,
|
|
34
|
-
baseUrl,
|
|
35
|
-
onProgress: arg => {
|
|
36
|
-
self.setProgress(arg)
|
|
37
|
-
},
|
|
38
|
-
})
|
|
39
|
-
hits = result.hits
|
|
40
|
-
rid = result.rid
|
|
41
|
-
} else {
|
|
42
|
-
const result = await queryBlast({
|
|
43
|
-
query: cleanedSeq,
|
|
44
|
-
blastDatabase,
|
|
45
|
-
blastProgram,
|
|
46
|
-
baseUrl,
|
|
47
|
-
onProgress: arg => {
|
|
48
|
-
self.setProgress(arg)
|
|
49
|
-
},
|
|
50
|
-
onRid: r => {
|
|
51
|
-
self.setRid(r)
|
|
52
|
-
},
|
|
53
|
-
})
|
|
54
|
-
hits = result.hits
|
|
55
|
-
rid = result.rid
|
|
56
36
|
}
|
|
57
37
|
|
|
38
|
+
const { hits, rid } = existingRid
|
|
39
|
+
? await queryBlastFromRid({ rid: existingRid, baseUrl, onProgress })
|
|
40
|
+
: await queryBlast({
|
|
41
|
+
query: cleanedSeq,
|
|
42
|
+
blastDatabase,
|
|
43
|
+
blastProgram,
|
|
44
|
+
baseUrl,
|
|
45
|
+
onProgress,
|
|
46
|
+
onRid: r => {
|
|
47
|
+
self.setRid(r)
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
|
|
58
51
|
self.setProgress('Fetching species taxonomy info...')
|
|
59
52
|
const taxids = hits
|
|
60
53
|
.map(h => h.description[0]?.taxid)
|
|
@@ -80,9 +73,7 @@ export async function doLaunchBlast({
|
|
|
80
73
|
const result = await launchMSA({
|
|
81
74
|
algorithm: msaAlgorithm,
|
|
82
75
|
sequence: [`>QUERY\n${cleanedSeq}`, ...sequences].join('\n'),
|
|
83
|
-
onProgress
|
|
84
|
-
self.setProgress(arg)
|
|
85
|
-
},
|
|
76
|
+
onProgress,
|
|
86
77
|
})
|
|
87
78
|
|
|
88
79
|
const treeMetadataJson = JSON.stringify(treeMetadata)
|
|
@@ -91,8 +91,9 @@ describe('genomeToMSA', () => {
|
|
|
91
91
|
|
|
92
92
|
const result = genomeToMSA({ model })
|
|
93
93
|
|
|
94
|
-
// coord 1005 -
|
|
95
|
-
|
|
94
|
+
// hover coord 1005 is 1-based, so the 0-based genome position is 1004,
|
|
95
|
+
// which is ungapped position 4 of a region starting at 1000
|
|
96
|
+
expect(mockSeqPosToVisibleCol).toHaveBeenCalledWith('hg38.chr1', 4)
|
|
96
97
|
expect(result).toBe(5)
|
|
97
98
|
})
|
|
98
99
|
|
|
@@ -125,10 +126,11 @@ describe('genomeToMSA', () => {
|
|
|
125
126
|
})
|
|
126
127
|
|
|
127
128
|
test('returns undefined when hover coord is before mafRegion start', () => {
|
|
129
|
+
// 1-based coord 1000 is the 0-based base 999, one before the region
|
|
128
130
|
mockGetSession.mockReturnValue({
|
|
129
131
|
hovered: {
|
|
130
132
|
hoverFeature: {},
|
|
131
|
-
hoverPosition: { coord:
|
|
133
|
+
hoverPosition: { coord: 1000, refName: 'chr1' },
|
|
132
134
|
},
|
|
133
135
|
} as any)
|
|
134
136
|
|
|
@@ -153,10 +155,11 @@ describe('genomeToMSA', () => {
|
|
|
153
155
|
})
|
|
154
156
|
|
|
155
157
|
test('returns undefined when hover coord is at or after mafRegion end', () => {
|
|
158
|
+
// 1-based coord 1011 is the 0-based base 1010, one past the region
|
|
156
159
|
mockGetSession.mockReturnValue({
|
|
157
160
|
hovered: {
|
|
158
161
|
hoverFeature: {},
|
|
159
|
-
hoverPosition: { coord:
|
|
162
|
+
hoverPosition: { coord: 1011, refName: 'chr1' },
|
|
160
163
|
},
|
|
161
164
|
} as any)
|
|
162
165
|
|
|
@@ -223,7 +226,9 @@ describe('genomeToMSA', () => {
|
|
|
223
226
|
const model = {
|
|
224
227
|
querySeqName: 'QUERY',
|
|
225
228
|
transcriptToMsaMap: {
|
|
226
|
-
|
|
229
|
+
refName: 'chr1',
|
|
230
|
+
// g2p is keyed by 0-based genome position, the hover coord is 1-based
|
|
231
|
+
g2p: { 1004: 10 },
|
|
227
232
|
},
|
|
228
233
|
mafRegion: undefined,
|
|
229
234
|
connectedView: { initialized: true },
|
|
@@ -236,6 +241,32 @@ describe('genomeToMSA', () => {
|
|
|
236
241
|
expect(result).toBe(10)
|
|
237
242
|
})
|
|
238
243
|
|
|
244
|
+
test('returns undefined when the hover is on another refName', () => {
|
|
245
|
+
// session.hovered is global, so a hover on an unrelated chromosome can
|
|
246
|
+
// carry a coordinate that happens to be a g2p key
|
|
247
|
+
mockGetSession.mockReturnValue({
|
|
248
|
+
hovered: {
|
|
249
|
+
hoverFeature: {},
|
|
250
|
+
hoverPosition: { coord: 1005, refName: 'chr2' },
|
|
251
|
+
},
|
|
252
|
+
} as any)
|
|
253
|
+
|
|
254
|
+
const mockSeqPosToVisibleCol = vi.fn()
|
|
255
|
+
const model = {
|
|
256
|
+
querySeqName: 'QUERY',
|
|
257
|
+
transcriptToMsaMap: {
|
|
258
|
+
refName: 'chr1',
|
|
259
|
+
g2p: { 1004: 10 },
|
|
260
|
+
},
|
|
261
|
+
mafRegion: undefined,
|
|
262
|
+
connectedView: { initialized: true },
|
|
263
|
+
seqPosToVisibleCol: mockSeqPosToVisibleCol,
|
|
264
|
+
} as any
|
|
265
|
+
|
|
266
|
+
expect(genomeToMSA({ model })).toBeUndefined()
|
|
267
|
+
expect(mockSeqPosToVisibleCol).not.toHaveBeenCalled()
|
|
268
|
+
})
|
|
269
|
+
|
|
239
270
|
test('returns undefined when g2p has no mapping for coord', () => {
|
|
240
271
|
mockGetSession.mockReturnValue({
|
|
241
272
|
hovered: {
|
|
@@ -247,7 +278,8 @@ describe('genomeToMSA', () => {
|
|
|
247
278
|
const model = {
|
|
248
279
|
querySeqName: 'QUERY',
|
|
249
280
|
transcriptToMsaMap: {
|
|
250
|
-
|
|
281
|
+
refName: 'chr1',
|
|
282
|
+
g2p: { 1000: 0 }, // No entry for 1004
|
|
251
283
|
},
|
|
252
284
|
mafRegion: undefined,
|
|
253
285
|
connectedView: { initialized: true },
|
|
@@ -12,22 +12,30 @@ export function genomeToMSA({ model }: { model: JBrowsePluginMsaViewModel }) {
|
|
|
12
12
|
return undefined
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
const { coord
|
|
15
|
+
const { coord, refName } = hovered.hoverPosition
|
|
16
|
+
|
|
17
|
+
// hoverPosition.coord is a 1-based display coordinate (core's pxToBp adds the
|
|
18
|
+
// +1), while g2p and mafRegion are keyed by 0-based genome position
|
|
19
|
+
const genomePos = coord - 1
|
|
16
20
|
|
|
17
21
|
if (mafRegion) {
|
|
18
22
|
if (
|
|
19
23
|
refName !== mafRegion.refName ||
|
|
20
24
|
!connectedView.assemblyNames.includes(mafRegion.assemblyName) ||
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
genomePos < mafRegion.start ||
|
|
26
|
+
genomePos >= mafRegion.end
|
|
23
27
|
) {
|
|
24
28
|
return undefined
|
|
25
29
|
}
|
|
26
|
-
return model.seqPosToVisibleCol(querySeqName,
|
|
30
|
+
return model.seqPosToVisibleCol(querySeqName, genomePos - mafRegion.start)
|
|
27
31
|
}
|
|
28
32
|
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
// session.hovered is global -- set by whichever LinearGenomeView the cursor
|
|
34
|
+
// was last over, on any assembly -- so the refName gate is load bearing:
|
|
35
|
+
// without it the same numeric coordinate on an unrelated chromosome matches a
|
|
36
|
+
// g2p key and lights up a column for a different locus
|
|
37
|
+
if (refName === transcriptToMsaMap?.refName) {
|
|
38
|
+
const seqPos = transcriptToMsaMap.g2p[genomePos]
|
|
31
39
|
if (seqPos !== undefined) {
|
|
32
40
|
return model.seqPosToVisibleCol(querySeqName, seqPos)
|
|
33
41
|
}
|
|
@@ -19,7 +19,10 @@ import {
|
|
|
19
19
|
storeDataToIndexedDB,
|
|
20
20
|
syncGenomeHoverToMsaColumn,
|
|
21
21
|
} from './afterCreateAutoruns'
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
msaCoordToGenomeCoord,
|
|
24
|
+
msaCoordToGenomeRegions,
|
|
25
|
+
} from './msaCoordToGenomeCoord'
|
|
23
26
|
|
|
24
27
|
import type { MafRegion, MsaViewInitState } from './types'
|
|
25
28
|
import type {
|
|
@@ -188,35 +191,37 @@ export default function stateModelFactory() {
|
|
|
188
191
|
.views(self => ({
|
|
189
192
|
/**
|
|
190
193
|
* #getter
|
|
191
|
-
* Genome
|
|
194
|
+
* Genome regions under the current MSA hover column. Suppressed on the LGV
|
|
192
195
|
* while it's being hovered (GenomeMouseoverHighlight shows the crisp 1bp
|
|
193
196
|
* marker there instead of this wider codon band).
|
|
194
197
|
*/
|
|
195
|
-
get
|
|
198
|
+
get connectedHoverHighlights(): IRegion[] {
|
|
196
199
|
const { mouseCol } = self
|
|
197
200
|
return mouseCol === undefined
|
|
198
|
-
?
|
|
199
|
-
:
|
|
201
|
+
? []
|
|
202
|
+
: msaCoordToGenomeRegions({ model: self, coord: mouseCol })
|
|
200
203
|
},
|
|
201
204
|
/**
|
|
202
205
|
* #getter
|
|
203
|
-
* Genome
|
|
206
|
+
* Genome regions under the persistent MSA click selection. Shown
|
|
204
207
|
* regardless of LGV hover, so hovering the genome doesn't hide it.
|
|
205
208
|
*/
|
|
206
|
-
get
|
|
209
|
+
get connectedClickHighlights(): IRegion[] {
|
|
207
210
|
const { mouseClickCol } = self
|
|
208
211
|
return mouseClickCol === undefined
|
|
209
|
-
?
|
|
210
|
-
:
|
|
212
|
+
? []
|
|
213
|
+
: msaCoordToGenomeRegions({ model: self, coord: mouseClickCol })
|
|
211
214
|
},
|
|
212
215
|
/**
|
|
213
216
|
* #getter
|
|
217
|
+
* cross-plugin contract: jbrowse-plugin-mafviewer reads this off the view
|
|
218
|
+
* to draw the same highlights in its own display
|
|
214
219
|
*/
|
|
215
220
|
get connectedHighlights(): IRegion[] {
|
|
216
221
|
return [
|
|
217
|
-
this.
|
|
218
|
-
this.
|
|
219
|
-
]
|
|
222
|
+
...this.connectedHoverHighlights,
|
|
223
|
+
...this.connectedClickHighlights,
|
|
224
|
+
]
|
|
220
225
|
},
|
|
221
226
|
}))
|
|
222
227
|
|