@jbrowse/plugin-variants 3.0.5 → 3.2.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/MultiLinearVariantDisplay/components/Crosshair.js +1 -1
- package/dist/MultiLinearVariantDisplay/model.d.ts +19 -0
- package/dist/MultiLinearVariantMatrixDisplay/components/Crosshair.js +1 -1
- package/dist/MultiLinearVariantMatrixDisplay/model.d.ts +20 -0
- package/dist/MultiLinearVariantMatrixDisplay/model.js +4 -1
- package/dist/MultiLinearVariantMatrixRenderer/makeImageData.d.ts +1 -0
- package/dist/MultiLinearVariantMatrixRenderer/makeImageData.js +9 -1
- package/dist/MultiLinearVariantRenderer/makeImageData.js +9 -1
- package/dist/VariantRPC/MultiVariantClusterGenotypeMatrix.d.ts +28 -0
- package/dist/VariantRPC/MultiVariantClusterGenotypeMatrix.js +33 -0
- package/dist/VariantRPC/MultiVariantGetGenotypeMatrix.d.ts +13 -11
- package/dist/VariantRPC/MultiVariantGetGenotypeMatrix.js +5 -34
- package/dist/VariantRPC/MultiVariantGetSimplifiedFeatures.d.ts +11 -8
- package/dist/VariantRPC/MultiVariantGetSimplifiedFeatures.js +1 -2
- package/dist/VariantRPC/cluster.d.ts +17 -0
- package/dist/VariantRPC/cluster.js +84 -0
- package/dist/VariantRPC/getGenotypeMatrix.d.ts +6 -0
- package/dist/VariantRPC/getGenotypeMatrix.js +55 -0
- package/dist/VariantRPC/types.d.ts +13 -0
- package/dist/VariantRPC/types.js +2 -0
- package/dist/VcfAdapter/VcfAdapter.js +6 -3
- package/dist/VcfAdapter/configSchema.js +14 -1
- package/dist/VcfFeature/util.js +2 -2
- package/dist/VcfTabixAdapter/VcfTabixAdapter.js +11 -6
- package/dist/VcfTabixAdapter/configSchema.js +24 -2
- package/dist/index.js +2 -0
- package/dist/shared/MultiVariantBaseModel.d.ts +19 -0
- package/dist/shared/MultiVariantBaseModel.js +27 -35
- package/dist/shared/components/ClusterDialog/ClusterDialog.d.ts +6 -0
- package/dist/shared/components/ClusterDialog/ClusterDialog.js +29 -0
- package/dist/shared/components/ClusterDialog/ClusterDialogAuto.d.ts +7 -0
- package/dist/shared/components/ClusterDialog/ClusterDialogAuto.js +69 -0
- package/dist/shared/components/ClusterDialog/ClusterDialogManual.d.ts +7 -0
- package/dist/shared/components/ClusterDialog/ClusterDialogManual.js +144 -0
- package/dist/shared/components/ClusterDialog/types.d.ts +9 -0
- package/dist/shared/components/ClusterDialog/types.js +2 -0
- package/dist/shared/components/SourcesDataGrid.js +47 -40
- package/dist/shared/components/SourcesGridHeader.js +2 -2
- package/dist/shared/getSources.d.ts +15 -0
- package/dist/shared/getSources.js +34 -0
- package/dist/shared/minorAlleleFrequencyUtils.d.ts +3 -5
- package/dist/shared/minorAlleleFrequencyUtils.js +13 -8
- package/esm/MultiLinearVariantDisplay/components/Crosshair.js +1 -1
- package/esm/MultiLinearVariantDisplay/model.d.ts +19 -0
- package/esm/MultiLinearVariantMatrixDisplay/components/Crosshair.js +1 -1
- package/esm/MultiLinearVariantMatrixDisplay/model.d.ts +20 -0
- package/esm/MultiLinearVariantMatrixDisplay/model.js +4 -1
- package/esm/MultiLinearVariantMatrixRenderer/makeImageData.d.ts +1 -0
- package/esm/MultiLinearVariantMatrixRenderer/makeImageData.js +9 -1
- package/esm/MultiLinearVariantRenderer/makeImageData.js +9 -1
- package/esm/VariantRPC/MultiVariantClusterGenotypeMatrix.d.ts +28 -0
- package/esm/VariantRPC/MultiVariantClusterGenotypeMatrix.js +26 -0
- package/esm/VariantRPC/MultiVariantGetGenotypeMatrix.d.ts +13 -11
- package/esm/VariantRPC/MultiVariantGetGenotypeMatrix.js +5 -34
- package/esm/VariantRPC/MultiVariantGetSimplifiedFeatures.d.ts +11 -8
- package/esm/VariantRPC/MultiVariantGetSimplifiedFeatures.js +1 -2
- package/esm/VariantRPC/cluster.d.ts +17 -0
- package/esm/VariantRPC/cluster.js +79 -0
- package/esm/VariantRPC/getGenotypeMatrix.d.ts +6 -0
- package/esm/VariantRPC/getGenotypeMatrix.js +52 -0
- package/esm/VariantRPC/types.d.ts +13 -0
- package/esm/VariantRPC/types.js +1 -0
- package/esm/VcfAdapter/VcfAdapter.js +7 -4
- package/esm/VcfAdapter/configSchema.js +14 -1
- package/esm/VcfFeature/util.js +2 -2
- package/esm/VcfTabixAdapter/VcfTabixAdapter.js +11 -6
- package/esm/VcfTabixAdapter/configSchema.js +24 -2
- package/esm/index.js +2 -0
- package/esm/shared/MultiVariantBaseModel.d.ts +19 -0
- package/esm/shared/MultiVariantBaseModel.js +27 -35
- package/esm/shared/components/ClusterDialog/ClusterDialog.d.ts +6 -0
- package/esm/shared/components/ClusterDialog/ClusterDialog.js +24 -0
- package/esm/shared/components/ClusterDialog/ClusterDialogAuto.d.ts +7 -0
- package/esm/shared/components/ClusterDialog/ClusterDialogAuto.js +67 -0
- package/esm/shared/components/ClusterDialog/ClusterDialogManual.d.ts +7 -0
- package/esm/shared/components/ClusterDialog/ClusterDialogManual.js +139 -0
- package/esm/shared/components/ClusterDialog/types.d.ts +9 -0
- package/esm/shared/components/ClusterDialog/types.js +1 -0
- package/esm/shared/components/SourcesDataGrid.js +47 -40
- package/esm/shared/components/SourcesGridHeader.js +2 -2
- package/esm/shared/getSources.d.ts +15 -0
- package/esm/shared/getSources.js +31 -0
- package/esm/shared/minorAlleleFrequencyUtils.d.ts +3 -5
- package/esm/shared/minorAlleleFrequencyUtils.js +13 -8
- package/package.json +6 -6
- package/dist/shared/components/ClusterDialog.d.ts +0 -11
- package/dist/shared/components/ClusterDialog.js +0 -109
- package/esm/shared/components/ClusterDialog.d.ts +0 -11
- package/esm/shared/components/ClusterDialog.js +0 -103
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import IntervalTree from '@flatten-js/interval-tree';
|
|
2
2
|
import VcfParser from '@gmod/vcf';
|
|
3
3
|
import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter';
|
|
4
|
-
import { fetchAndMaybeUnzip } from '@jbrowse/core/util';
|
|
4
|
+
import { fetchAndMaybeUnzip, getProgressDisplayStr } from '@jbrowse/core/util';
|
|
5
5
|
import { openLocation } from '@jbrowse/core/util/io';
|
|
6
6
|
import { ObservableCreate } from '@jbrowse/core/util/rxjs';
|
|
7
7
|
import VcfFeature from '../VcfFeature';
|
|
@@ -45,7 +45,7 @@ class VcfAdapter extends BaseFeatureDataAdapter {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
if (i++ % 10000 === 0) {
|
|
48
|
-
statusCallback(`Loading ${
|
|
48
|
+
statusCallback(`Loading ${getProgressDisplayStr(blockStart, buffer.length)}`);
|
|
49
49
|
}
|
|
50
50
|
blockStart = n + 1;
|
|
51
51
|
}
|
|
@@ -96,9 +96,12 @@ class VcfAdapter extends BaseFeatureDataAdapter {
|
|
|
96
96
|
try {
|
|
97
97
|
const { start, end, refName } = region;
|
|
98
98
|
const { intervalTreeMap } = await this.setup();
|
|
99
|
-
(_a = intervalTreeMap[refName]) === null || _a === void 0 ? void 0 : _a.call(intervalTreeMap, opts.statusCallback).search([
|
|
99
|
+
for (const f of ((_a = intervalTreeMap[refName]) === null || _a === void 0 ? void 0 : _a.call(intervalTreeMap, opts.statusCallback).search([
|
|
100
|
+
start,
|
|
101
|
+
end,
|
|
102
|
+
])) || []) {
|
|
100
103
|
observer.next(f);
|
|
101
|
-
}
|
|
104
|
+
}
|
|
102
105
|
observer.complete();
|
|
103
106
|
}
|
|
104
107
|
catch (e) {
|
|
@@ -16,5 +16,18 @@ const VcfAdapter = ConfigurationSchema('VcfAdapter', {
|
|
|
16
16
|
locationType: 'UriLocation',
|
|
17
17
|
},
|
|
18
18
|
},
|
|
19
|
-
}, {
|
|
19
|
+
}, {
|
|
20
|
+
explicitlyTyped: true,
|
|
21
|
+
preProcessSnapshot: snap => {
|
|
22
|
+
return snap.uri
|
|
23
|
+
? {
|
|
24
|
+
...snap,
|
|
25
|
+
vcfLocation: {
|
|
26
|
+
uri: snap.uri,
|
|
27
|
+
baseUri: snap.baseUri,
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
: snap;
|
|
31
|
+
},
|
|
32
|
+
});
|
|
20
33
|
export default VcfAdapter;
|
package/esm/VcfFeature/util.js
CHANGED
|
@@ -17,7 +17,7 @@ export function getSOTermAndDescription(ref, alt, parser) {
|
|
|
17
17
|
}
|
|
18
18
|
const soTerms = new Set();
|
|
19
19
|
let descriptions = new Set();
|
|
20
|
-
|
|
20
|
+
for (const a of alt) {
|
|
21
21
|
let [soTerm, description] = getSOAndDescFromAltDefs(a, parser);
|
|
22
22
|
if (!soTerm) {
|
|
23
23
|
;
|
|
@@ -27,7 +27,7 @@ export function getSOTermAndDescription(ref, alt, parser) {
|
|
|
27
27
|
soTerms.add(soTerm);
|
|
28
28
|
descriptions.add(description);
|
|
29
29
|
}
|
|
30
|
-
}
|
|
30
|
+
}
|
|
31
31
|
if (descriptions.size > 1) {
|
|
32
32
|
const descs = [...descriptions];
|
|
33
33
|
const prefixes = new Set(descs
|
|
@@ -86,16 +86,21 @@ export default class VcfTabixAdapter extends BaseFeatureDataAdapter {
|
|
|
86
86
|
const header = lines[0].split('\t');
|
|
87
87
|
const { parser } = await this.configure();
|
|
88
88
|
const s = new Set(parser.samples);
|
|
89
|
-
|
|
89
|
+
const ret = lines
|
|
90
90
|
.slice(1)
|
|
91
|
+
.filter(f => !!f)
|
|
91
92
|
.map(line => {
|
|
92
|
-
const
|
|
93
|
+
const [name, ...rest] = line.split('\t');
|
|
93
94
|
return {
|
|
94
|
-
|
|
95
|
-
|
|
95
|
+
...Object.fromEntries(rest.map((c, idx) => [header[idx + 1], c])),
|
|
96
|
+
name: name,
|
|
96
97
|
};
|
|
97
|
-
})
|
|
98
|
-
|
|
98
|
+
});
|
|
99
|
+
const missing = ret.filter(f => !s.has(f.name));
|
|
100
|
+
if (missing.length) {
|
|
101
|
+
console.warn('Samples in metadata file not in VCF:', ret.filter(f => !s.has(f.name)));
|
|
102
|
+
}
|
|
103
|
+
return ret.filter(f => s.has(f.name));
|
|
99
104
|
}
|
|
100
105
|
}
|
|
101
106
|
freeResources() { }
|
|
@@ -4,7 +4,10 @@ function x() { }
|
|
|
4
4
|
const VcfTabixAdapter = ConfigurationSchema('VcfTabixAdapter', {
|
|
5
5
|
vcfGzLocation: {
|
|
6
6
|
type: 'fileLocation',
|
|
7
|
-
defaultValue: {
|
|
7
|
+
defaultValue: {
|
|
8
|
+
uri: '/path/to/my.vcf.gz',
|
|
9
|
+
locationType: 'UriLocation',
|
|
10
|
+
},
|
|
8
11
|
},
|
|
9
12
|
index: ConfigurationSchema('VcfIndex', {
|
|
10
13
|
indexType: {
|
|
@@ -28,5 +31,24 @@ const VcfTabixAdapter = ConfigurationSchema('VcfTabixAdapter', {
|
|
|
28
31
|
locationType: 'UriLocation',
|
|
29
32
|
},
|
|
30
33
|
},
|
|
31
|
-
}, {
|
|
34
|
+
}, {
|
|
35
|
+
explicitlyTyped: true,
|
|
36
|
+
preProcessSnapshot: snap => {
|
|
37
|
+
return snap.uri
|
|
38
|
+
? {
|
|
39
|
+
...snap,
|
|
40
|
+
vcfGzLocation: {
|
|
41
|
+
uri: snap.uri,
|
|
42
|
+
baseUri: snap.baseUri,
|
|
43
|
+
},
|
|
44
|
+
index: {
|
|
45
|
+
location: {
|
|
46
|
+
uri: `${snap.uri}.tbi`,
|
|
47
|
+
baseUri: snap.baseUri,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
: snap;
|
|
52
|
+
},
|
|
53
|
+
});
|
|
32
54
|
export default VcfTabixAdapter;
|
package/esm/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import MultiVariantRendererF from './MultiLinearVariantRenderer';
|
|
|
8
8
|
import SplitVcfTabixAdapterF from './SplitVcfTabixAdapter';
|
|
9
9
|
import StructuralVariantChordRendererF from './StructuralVariantChordRenderer';
|
|
10
10
|
import VariantFeatureWidgetF from './VariantFeatureWidget';
|
|
11
|
+
import { MultiVariantClusterGenotypeMatrix } from './VariantRPC/MultiVariantClusterGenotypeMatrix';
|
|
11
12
|
import { MultiVariantGetGenotypeMatrix } from './VariantRPC/MultiVariantGetGenotypeMatrix';
|
|
12
13
|
import { MultiVariantGetSimplifiedFeatures } from './VariantRPC/MultiVariantGetSimplifiedFeatures';
|
|
13
14
|
import { MultiVariantGetSources } from './VariantRPC/MultiVariantGetSources';
|
|
@@ -36,6 +37,7 @@ export default class VariantsPlugin extends Plugin {
|
|
|
36
37
|
ChordVariantDisplayF(pluginManager);
|
|
37
38
|
pluginManager.addRpcMethod(() => new MultiVariantGetSources(pluginManager));
|
|
38
39
|
pluginManager.addRpcMethod(() => new MultiVariantGetGenotypeMatrix(pluginManager));
|
|
40
|
+
pluginManager.addRpcMethod(() => new MultiVariantClusterGenotypeMatrix(pluginManager));
|
|
39
41
|
pluginManager.addRpcMethod(() => new MultiVariantGetSimplifiedFeatures(pluginManager));
|
|
40
42
|
}
|
|
41
43
|
}
|
|
@@ -255,6 +255,15 @@ export default function MultiVariantBaseModelF(configSchema: AnyConfigurationSch
|
|
|
255
255
|
setSampleInfo(arg: Record<string, SampleInfo>): void;
|
|
256
256
|
} & {
|
|
257
257
|
readonly preSources: Source[] | undefined;
|
|
258
|
+
readonly sourcesWithoutLayout: {
|
|
259
|
+
label: string;
|
|
260
|
+
id: string;
|
|
261
|
+
baseUri?: string;
|
|
262
|
+
name: string;
|
|
263
|
+
color?: string;
|
|
264
|
+
group?: string;
|
|
265
|
+
HP?: number;
|
|
266
|
+
}[] | undefined;
|
|
258
267
|
readonly sources: {
|
|
259
268
|
label: string;
|
|
260
269
|
id: string;
|
|
@@ -330,10 +339,20 @@ export default function MultiVariantBaseModelF(configSchema: AnyConfigurationSch
|
|
|
330
339
|
type?: undefined;
|
|
331
340
|
checked?: undefined;
|
|
332
341
|
onClick?: undefined;
|
|
342
|
+
} | {
|
|
343
|
+
label: string;
|
|
344
|
+
icon: import("@mui/material/OverridableComponent").OverridableComponent<import("@mui/material").SvgIconTypeMap<{}, "svg">> & {
|
|
345
|
+
muiName: string;
|
|
346
|
+
};
|
|
347
|
+
onClick: () => void;
|
|
348
|
+
type?: undefined;
|
|
349
|
+
checked?: undefined;
|
|
350
|
+
subMenu?: undefined;
|
|
333
351
|
})[];
|
|
334
352
|
} & {
|
|
335
353
|
readonly canDisplayLabels: boolean;
|
|
336
354
|
readonly totalHeight: number;
|
|
355
|
+
readonly featuresReady: boolean;
|
|
337
356
|
} & {
|
|
338
357
|
renderProps(): any;
|
|
339
358
|
}, import("mobx-state-tree")._NotCustomized, import("mobx-state-tree")._NotCustomized>;
|
|
@@ -3,15 +3,17 @@ import { ConfigurationReference } from '@jbrowse/core/configuration';
|
|
|
3
3
|
import { getSession } from '@jbrowse/core/util';
|
|
4
4
|
import { stopStopToken } from '@jbrowse/core/util/stopToken';
|
|
5
5
|
import { linearBareDisplayStateModelFactory } from '@jbrowse/plugin-linear-genome-view';
|
|
6
|
+
import CategoryIcon from '@mui/icons-material/Category';
|
|
6
7
|
import FilterListIcon from '@mui/icons-material/FilterList';
|
|
7
8
|
import HeightIcon from '@mui/icons-material/Height';
|
|
8
9
|
import SplitscreenIcon from '@mui/icons-material/Splitscreen';
|
|
9
10
|
import VisibilityIcon from '@mui/icons-material/Visibility';
|
|
10
11
|
import deepEqual from 'fast-deep-equal';
|
|
11
12
|
import { types } from 'mobx-state-tree';
|
|
13
|
+
import { getSources } from './getSources';
|
|
12
14
|
const SetColorDialog = lazy(() => import('./components/SetColorDialog'));
|
|
13
15
|
const MAFFilterDialog = lazy(() => import('./components/MAFFilterDialog'));
|
|
14
|
-
const ClusterDialog = lazy(() => import('./components/ClusterDialog'));
|
|
16
|
+
const ClusterDialog = lazy(() => import('./components/ClusterDialog/ClusterDialog'));
|
|
15
17
|
const SetRowHeightDialog = lazy(() => import('./components/SetRowHeightDialog'));
|
|
16
18
|
export default function MultiVariantBaseModelF(configSchema) {
|
|
17
19
|
return types
|
|
@@ -86,40 +88,26 @@ export default function MultiVariantBaseModelF(configSchema) {
|
|
|
86
88
|
get preSources() {
|
|
87
89
|
return self.layout.length ? self.layout : self.sourcesVolatile;
|
|
88
90
|
},
|
|
91
|
+
get sourcesWithoutLayout() {
|
|
92
|
+
return self.sourcesVolatile
|
|
93
|
+
? getSources({
|
|
94
|
+
sources: self.sourcesVolatile,
|
|
95
|
+
renderingMode: self.renderingMode,
|
|
96
|
+
sampleInfo: self.sampleInfo,
|
|
97
|
+
})
|
|
98
|
+
: undefined;
|
|
99
|
+
},
|
|
89
100
|
get sources() {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const id = `${row.name} HP${i}`;
|
|
101
|
-
rows.push({
|
|
102
|
-
...sources[row.name],
|
|
103
|
-
...row,
|
|
104
|
-
label: id,
|
|
105
|
-
HP: i,
|
|
106
|
-
id: id,
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
else {
|
|
112
|
-
rows.push({
|
|
113
|
-
...sources[row.name],
|
|
114
|
-
...row,
|
|
115
|
-
label: row.name,
|
|
116
|
-
id: row.name,
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return rows;
|
|
121
|
-
}
|
|
122
|
-
return undefined;
|
|
101
|
+
const sourcesWithLayout = self.layout.length
|
|
102
|
+
? self.layout
|
|
103
|
+
: self.sourcesVolatile;
|
|
104
|
+
return sourcesWithLayout
|
|
105
|
+
? getSources({
|
|
106
|
+
sources: sourcesWithLayout,
|
|
107
|
+
renderingMode: self.renderingMode,
|
|
108
|
+
sampleInfo: self.sampleInfo,
|
|
109
|
+
})
|
|
110
|
+
: undefined;
|
|
123
111
|
},
|
|
124
112
|
}))
|
|
125
113
|
.views(self => {
|
|
@@ -222,6 +210,7 @@ export default function MultiVariantBaseModelF(configSchema) {
|
|
|
222
210
|
},
|
|
223
211
|
{
|
|
224
212
|
label: 'Cluster by genotype',
|
|
213
|
+
icon: CategoryIcon,
|
|
225
214
|
onClick: () => {
|
|
226
215
|
getSession(self).queueDialog(handleClose => [
|
|
227
216
|
ClusterDialog,
|
|
@@ -256,13 +245,16 @@ export default function MultiVariantBaseModelF(configSchema) {
|
|
|
256
245
|
var _a;
|
|
257
246
|
return self.rowHeight * (((_a = self.sources) === null || _a === void 0 ? void 0 : _a.length) || 1);
|
|
258
247
|
},
|
|
248
|
+
get featuresReady() {
|
|
249
|
+
return !!self.featuresVolatile;
|
|
250
|
+
},
|
|
259
251
|
}))
|
|
260
252
|
.views(self => ({
|
|
261
253
|
renderProps() {
|
|
262
254
|
const superProps = self.adapterProps();
|
|
263
255
|
return {
|
|
264
256
|
...superProps,
|
|
265
|
-
notReady: superProps.notReady || !self.sources || !self.
|
|
257
|
+
notReady: superProps.notReady || !self.sources || !self.featuresReady,
|
|
266
258
|
height: self.height,
|
|
267
259
|
totalHeight: self.totalHeight,
|
|
268
260
|
renderingMode: self.renderingMode,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Dialog } from '@jbrowse/core/ui';
|
|
4
|
+
import { FormControlLabel, Radio, RadioGroup, Typography } from '@mui/material';
|
|
5
|
+
import { observer } from 'mobx-react';
|
|
6
|
+
import ClusterDialogAuto from './ClusterDialogAuto';
|
|
7
|
+
import ClusterDialogManual from './ClusterDialogManual';
|
|
8
|
+
function Header({ activeMode, setActiveMode, }) {
|
|
9
|
+
return (_jsxs("div", { children: [_jsx(Typography, { style: { marginBottom: 30 }, children: "This procedure will cluster the visible genotype data using hierarchical clustering" }), _jsx(RadioGroup, { children: Object.entries({
|
|
10
|
+
auto: (_jsx("div", { children: "Run in-app clustering (slow for large data, built in JS clustering)" })),
|
|
11
|
+
manual: (_jsx("div", { children: "Download R script to run clustering (faster for large data, uses hclust, may be more accurate)" })),
|
|
12
|
+
}).map(([key, val]) => (_jsx(FormControlLabel, { control: _jsx(Radio, { checked: activeMode === key, onChange: () => {
|
|
13
|
+
setActiveMode(key);
|
|
14
|
+
} }), label: val }, key))) })] }));
|
|
15
|
+
}
|
|
16
|
+
const ClusterDialog = observer(function ({ model, handleClose, }) {
|
|
17
|
+
const [activeMode, setActiveMode] = useState('auto');
|
|
18
|
+
return (_jsx(Dialog, { open: true, title: "Cluster by genotype", onClose: (_, reason) => {
|
|
19
|
+
if (reason !== 'backdropClick') {
|
|
20
|
+
handleClose();
|
|
21
|
+
}
|
|
22
|
+
}, children: activeMode === 'auto' ? (_jsx(ClusterDialogAuto, { model: model, handleClose: handleClose, children: _jsx(Header, { activeMode: activeMode, setActiveMode: setActiveMode }) })) : (_jsx(ClusterDialogManual, { model: model, handleClose: handleClose, children: _jsx(Header, { activeMode: activeMode, setActiveMode: setActiveMode }) })) }));
|
|
23
|
+
});
|
|
24
|
+
export default ClusterDialog;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ReducedModel } from './types';
|
|
2
|
+
declare const ClusterDialogAuto: ({ model, children, handleClose, }: {
|
|
3
|
+
model: ReducedModel;
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
handleClose: () => void;
|
|
6
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export default ClusterDialogAuto;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { ErrorMessage } from '@jbrowse/core/ui';
|
|
4
|
+
import { getContainingView, getSession, isAbortException, } from '@jbrowse/core/util';
|
|
5
|
+
import { createStopToken, stopStopToken } from '@jbrowse/core/util/stopToken';
|
|
6
|
+
import { getRpcSessionId } from '@jbrowse/core/util/tracks';
|
|
7
|
+
import { Button, DialogActions, DialogContent } from '@mui/material';
|
|
8
|
+
import { observer } from 'mobx-react';
|
|
9
|
+
import { isAlive } from 'mobx-state-tree';
|
|
10
|
+
const ClusterDialogAuto = observer(function ({ model, children, handleClose, }) {
|
|
11
|
+
const [progress, setProgress] = useState('');
|
|
12
|
+
const [error, setError] = useState();
|
|
13
|
+
const [stopToken, setStopToken] = useState('');
|
|
14
|
+
return (_jsxs(_Fragment, { children: [_jsxs(DialogContent, { children: [children, _jsxs("div", { children: [progress ? (_jsxs("div", { style: { padding: 50 }, children: [_jsxs("span", { style: { width: 400 }, children: ["Progress: ", progress] }), _jsx(Button, { onClick: () => {
|
|
15
|
+
stopStopToken(stopToken);
|
|
16
|
+
}, children: "Stop" })] })) : null, error ? _jsx(ErrorMessage, { error: error }) : null] })] }), _jsxs(DialogActions, { children: [_jsx(Button, { variant: "contained", onClick: async () => {
|
|
17
|
+
try {
|
|
18
|
+
setError(undefined);
|
|
19
|
+
const view = getContainingView(model);
|
|
20
|
+
if (!view.initialized) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const { rpcManager } = getSession(model);
|
|
24
|
+
const { sourcesWithoutLayout, minorAlleleFrequencyFilter, adapterConfig, } = model;
|
|
25
|
+
if (sourcesWithoutLayout) {
|
|
26
|
+
const sessionId = getRpcSessionId(model);
|
|
27
|
+
const stopToken = createStopToken();
|
|
28
|
+
setStopToken(stopToken);
|
|
29
|
+
const ret = (await rpcManager.call(sessionId, 'MultiVariantClusterGenotypeMatrix', {
|
|
30
|
+
regions: view.dynamicBlocks.contentBlocks,
|
|
31
|
+
sources: sourcesWithoutLayout,
|
|
32
|
+
minorAlleleFrequencyFilter,
|
|
33
|
+
sessionId,
|
|
34
|
+
adapterConfig,
|
|
35
|
+
stopToken,
|
|
36
|
+
statusCallback: (arg) => {
|
|
37
|
+
setProgress(arg);
|
|
38
|
+
},
|
|
39
|
+
}));
|
|
40
|
+
model.setLayout(ret.order.map(idx => {
|
|
41
|
+
const ret = sourcesWithoutLayout[idx];
|
|
42
|
+
if (!ret) {
|
|
43
|
+
throw new Error(`out of bounds at ${idx}`);
|
|
44
|
+
}
|
|
45
|
+
return ret;
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
handleClose();
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
if (!isAbortException(e) && isAlive(model)) {
|
|
52
|
+
console.error(e);
|
|
53
|
+
setError(e);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
setProgress('');
|
|
58
|
+
setStopToken('');
|
|
59
|
+
}
|
|
60
|
+
}, children: "Run clustering" }), _jsx(Button, { variant: "contained", color: "secondary", onClick: () => {
|
|
61
|
+
handleClose();
|
|
62
|
+
if (stopToken) {
|
|
63
|
+
stopStopToken(stopToken);
|
|
64
|
+
}
|
|
65
|
+
}, children: "Cancel" })] })] }));
|
|
66
|
+
});
|
|
67
|
+
export default ClusterDialogAuto;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ReducedModel } from './types';
|
|
2
|
+
declare const ClusterDialogManuals: ({ model, handleClose, children, }: {
|
|
3
|
+
model: ReducedModel;
|
|
4
|
+
handleClose: () => void;
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export default ClusterDialogManuals;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { ErrorMessage, LoadingEllipses } from '@jbrowse/core/ui';
|
|
4
|
+
import { getContainingView, getSession, isAbortException, useLocalStorage, } from '@jbrowse/core/util';
|
|
5
|
+
import { getRpcSessionId } from '@jbrowse/core/util/tracks';
|
|
6
|
+
import { Button, DialogActions, DialogContent, FormControlLabel, Paper, Radio, RadioGroup, TextField, Typography, } from '@mui/material';
|
|
7
|
+
import copy from 'copy-to-clipboard';
|
|
8
|
+
import { saveAs } from 'file-saver';
|
|
9
|
+
import { observer } from 'mobx-react';
|
|
10
|
+
import { isAlive } from 'mobx-state-tree';
|
|
11
|
+
import { makeStyles } from 'tss-react/mui';
|
|
12
|
+
const useStyles = makeStyles()(theme => ({
|
|
13
|
+
textAreaFont: {
|
|
14
|
+
fontFamily: 'Courier New',
|
|
15
|
+
},
|
|
16
|
+
mgap: {
|
|
17
|
+
display: 'flex',
|
|
18
|
+
flexDirection: 'column',
|
|
19
|
+
gap: theme.spacing(4),
|
|
20
|
+
},
|
|
21
|
+
}));
|
|
22
|
+
const ClusterDialogManuals = observer(function ({ model, handleClose, children, }) {
|
|
23
|
+
const { classes } = useStyles();
|
|
24
|
+
const [paste, setPaste] = useState('');
|
|
25
|
+
const [ret, setRet] = useState();
|
|
26
|
+
const [error, setError] = useState();
|
|
27
|
+
const [loading, setLoading] = useState(false);
|
|
28
|
+
const [showAdvanced, setShowAdvanced] = useState(false);
|
|
29
|
+
const [clusterMethod, setClusterMethod] = useLocalStorage('cluster-clusterMethod', 'single');
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
;
|
|
32
|
+
(async () => {
|
|
33
|
+
try {
|
|
34
|
+
setError(undefined);
|
|
35
|
+
setRet(undefined);
|
|
36
|
+
setLoading(true);
|
|
37
|
+
const view = getContainingView(model);
|
|
38
|
+
if (!view.initialized) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const { rpcManager } = getSession(model);
|
|
42
|
+
const { sourcesWithoutLayout, minorAlleleFrequencyFilter, adapterConfig, } = model;
|
|
43
|
+
const sessionId = getRpcSessionId(model);
|
|
44
|
+
const ret = (await rpcManager.call(sessionId, 'MultiVariantGetGenotypeMatrix', {
|
|
45
|
+
regions: view.dynamicBlocks.contentBlocks,
|
|
46
|
+
sources: sourcesWithoutLayout,
|
|
47
|
+
minorAlleleFrequencyFilter,
|
|
48
|
+
sessionId,
|
|
49
|
+
adapterConfig,
|
|
50
|
+
}));
|
|
51
|
+
setRet(ret);
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
if (!isAbortException(e) && isAlive(model)) {
|
|
55
|
+
console.error(e);
|
|
56
|
+
setError(e);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
setLoading(false);
|
|
61
|
+
}
|
|
62
|
+
})();
|
|
63
|
+
}, [model]);
|
|
64
|
+
const results = ret
|
|
65
|
+
? `inputMatrix<-matrix(c(${Object.values(ret)
|
|
66
|
+
.map(val => val.join(','))
|
|
67
|
+
.join(',\n')}
|
|
68
|
+
),nrow=${Object.values(ret).length},byrow=TRUE)
|
|
69
|
+
rownames(inputMatrix)<-c(${Object.keys(ret)
|
|
70
|
+
.map(key => `'${key}'`)
|
|
71
|
+
.join(',')})
|
|
72
|
+
resultClusters<-hclust(dist(inputMatrix), method='${clusterMethod}')
|
|
73
|
+
cat(resultClusters$order,sep='\\n')`
|
|
74
|
+
: undefined;
|
|
75
|
+
const resultsTsv = ret
|
|
76
|
+
? Object.entries(ret)
|
|
77
|
+
.map(([key, val]) => [key, ...val].join('\t'))
|
|
78
|
+
.join('\n')
|
|
79
|
+
: undefined;
|
|
80
|
+
return (_jsxs(_Fragment, { children: [_jsxs(DialogContent, { children: [children, _jsxs(Paper, { style: { padding: 16 }, children: [_jsxs("div", { style: {
|
|
81
|
+
display: 'flex',
|
|
82
|
+
gap: '8px',
|
|
83
|
+
flexWrap: 'wrap',
|
|
84
|
+
marginBottom: '16px',
|
|
85
|
+
}, children: [_jsx(Button, { variant: "contained", onClick: () => {
|
|
86
|
+
saveAs(new Blob([results || ''], {
|
|
87
|
+
type: 'text/plain;charset=utf-8',
|
|
88
|
+
}), 'cluster.R');
|
|
89
|
+
}, children: "Download Rscript" }), ' ', "or", ' ', _jsx(Button, { variant: "contained", onClick: () => {
|
|
90
|
+
copy(results || '');
|
|
91
|
+
}, children: "Copy Rscript to clipboard" }), ' ', "or", ' ', _jsx(Button, { variant: "contained", onClick: () => {
|
|
92
|
+
saveAs(new Blob([resultsTsv || ''], {
|
|
93
|
+
type: 'text/plain;charset=utf-8',
|
|
94
|
+
}), 'genotypes.tsv');
|
|
95
|
+
}, children: "Download TSV" }), _jsxs("div", { children: [_jsx(Button, { variant: "contained", onClick: () => {
|
|
96
|
+
setShowAdvanced(!showAdvanced);
|
|
97
|
+
}, children: showAdvanced
|
|
98
|
+
? 'Hide advanced options'
|
|
99
|
+
: 'Show advanced options' }), showAdvanced ? (_jsxs("div", { children: [_jsx(Typography, { variant: "h6", children: "Advanced options" }), _jsx(RadioGroup, { children: Object.entries({
|
|
100
|
+
single: 'Single',
|
|
101
|
+
complete: 'Complete',
|
|
102
|
+
}).map(([key, val]) => (_jsx(FormControlLabel, { control: _jsx(Radio, { checked: clusterMethod === key, onChange: () => {
|
|
103
|
+
setClusterMethod(key);
|
|
104
|
+
} }), label: val }, key))) })] })) : null] }), results ? (_jsx("div", {})) : loading ? (_jsx(LoadingEllipses, { variant: "h6", title: "Generating genotype matrix" })) : error ? (_jsx(ErrorMessage, { error: error })) : null] }), _jsxs("div", { children: [_jsx(Typography, { variant: "subtitle2", gutterBottom: true, style: { marginTop: '16px' }, children: "Clustering Results:" }), _jsx(TextField, { multiline: true, fullWidth: true, variant: "outlined", placeholder: "Paste results from Rscript here (sequence of numbers, one per line, specifying the new ordering)", rows: 10, value: paste, onChange: event => {
|
|
105
|
+
setPaste(event.target.value);
|
|
106
|
+
}, slotProps: {
|
|
107
|
+
input: {
|
|
108
|
+
classes: {
|
|
109
|
+
input: classes.textAreaFont,
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
} })] })] })] }), _jsxs(DialogActions, { children: [_jsx(Button, { variant: "contained", onClick: () => {
|
|
113
|
+
const { sourcesWithoutLayout } = model;
|
|
114
|
+
if (sourcesWithoutLayout) {
|
|
115
|
+
try {
|
|
116
|
+
model.setLayout(paste
|
|
117
|
+
.split('\n')
|
|
118
|
+
.map(t => t.trim())
|
|
119
|
+
.filter(f => !!f)
|
|
120
|
+
.map(r => +r)
|
|
121
|
+
.map(idx => {
|
|
122
|
+
const ret = sourcesWithoutLayout[idx - 1];
|
|
123
|
+
if (!ret) {
|
|
124
|
+
throw new Error(`out of bounds at ${idx}`);
|
|
125
|
+
}
|
|
126
|
+
return ret;
|
|
127
|
+
}));
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
console.error(e);
|
|
131
|
+
getSession(model).notifyError(`${e}`, e);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
handleClose();
|
|
135
|
+
}, children: "Apply clustering" }), _jsx(Button, { variant: "contained", color: "secondary", onClick: () => {
|
|
136
|
+
handleClose();
|
|
137
|
+
}, children: "Cancel" })] })] }));
|
|
138
|
+
});
|
|
139
|
+
export default ClusterDialogManuals;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Source } from '../../types';
|
|
2
|
+
import type { AnyConfigurationModel } from '@jbrowse/core/configuration';
|
|
3
|
+
export interface ReducedModel {
|
|
4
|
+
sourcesWithoutLayout?: Source[];
|
|
5
|
+
minorAlleleFrequencyFilter?: number;
|
|
6
|
+
adapterConfig: AnyConfigurationModel;
|
|
7
|
+
setLayout: (arg: Source[]) => void;
|
|
8
|
+
clearLayout: () => void;
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|