@jbrowse/plugin-wiggle 3.0.5 → 3.1.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/BigWigAdapter/configSchema.js +14 -1
- package/dist/MultiLinearWiggleDisplay/components/ClusterDialog.d.ts +11 -0
- package/dist/MultiLinearWiggleDisplay/components/ClusterDialog.js +115 -0
- package/dist/MultiLinearWiggleDisplay/model.js +17 -1
- package/dist/MultiLinearWiggleDisplay/types.d.ts +14 -0
- package/dist/MultiLinearWiggleDisplay/types.js +2 -0
- package/dist/WiggleRPC/MultiWiggleGetScoreMatrix.d.ts +17 -0
- package/dist/WiggleRPC/MultiWiggleGetScoreMatrix.js +54 -0
- package/dist/WiggleRPC/rpcMethods.d.ts +1 -0
- package/dist/WiggleRPC/rpcMethods.js +1 -0
- package/dist/drawXY.js +8 -2
- package/dist/index.js +1 -0
- package/esm/BigWigAdapter/configSchema.js +14 -1
- package/esm/MultiLinearWiggleDisplay/components/ClusterDialog.d.ts +11 -0
- package/esm/MultiLinearWiggleDisplay/components/ClusterDialog.js +109 -0
- package/esm/MultiLinearWiggleDisplay/model.js +17 -1
- package/esm/MultiLinearWiggleDisplay/types.d.ts +14 -0
- package/esm/MultiLinearWiggleDisplay/types.js +1 -0
- package/esm/WiggleRPC/MultiWiggleGetScoreMatrix.d.ts +17 -0
- package/esm/WiggleRPC/MultiWiggleGetScoreMatrix.js +47 -0
- package/esm/WiggleRPC/rpcMethods.d.ts +1 -0
- package/esm/WiggleRPC/rpcMethods.js +1 -0
- package/esm/drawXY.js +8 -2
- package/esm/index.js +2 -1
- package/package.json +5 -5
|
@@ -20,5 +20,18 @@ const BigWigAdapter = (0, configuration_1.ConfigurationSchema)('BigWigAdapter',
|
|
|
20
20
|
defaultValue: 1,
|
|
21
21
|
description: 'Initial resolution multiplier',
|
|
22
22
|
},
|
|
23
|
-
}, {
|
|
23
|
+
}, {
|
|
24
|
+
explicitlyTyped: true,
|
|
25
|
+
preProcessSnapshot: snap => {
|
|
26
|
+
return snap.uri
|
|
27
|
+
? {
|
|
28
|
+
...snap,
|
|
29
|
+
bigWigLocation: {
|
|
30
|
+
uri: snap.uri,
|
|
31
|
+
baseUri: snap.baseUri,
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
: snap;
|
|
35
|
+
},
|
|
36
|
+
});
|
|
24
37
|
exports.default = BigWigAdapter;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Source } from '../types';
|
|
2
|
+
import type { AnyConfigurationModel } from '@jbrowse/core/configuration';
|
|
3
|
+
export default function ClusterDialog({ model, handleClose, }: {
|
|
4
|
+
model: {
|
|
5
|
+
sources?: Source[];
|
|
6
|
+
minorAlleleFrequencyFilter?: number;
|
|
7
|
+
adapterConfig: AnyConfigurationModel;
|
|
8
|
+
setLayout: (arg: Source[]) => void;
|
|
9
|
+
};
|
|
10
|
+
handleClose: () => void;
|
|
11
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.default = ClusterDialog;
|
|
7
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
|
+
const react_1 = require("react");
|
|
9
|
+
const ui_1 = require("@jbrowse/core/ui");
|
|
10
|
+
const util_1 = require("@jbrowse/core/util");
|
|
11
|
+
const tracks_1 = require("@jbrowse/core/util/tracks");
|
|
12
|
+
const material_1 = require("@mui/material");
|
|
13
|
+
const copy_to_clipboard_1 = __importDefault(require("copy-to-clipboard"));
|
|
14
|
+
const file_saver_1 = require("file-saver");
|
|
15
|
+
const mobx_state_tree_1 = require("mobx-state-tree");
|
|
16
|
+
const mui_1 = require("tss-react/mui");
|
|
17
|
+
const useStyles = (0, mui_1.makeStyles)()(theme => ({
|
|
18
|
+
textAreaFont: {
|
|
19
|
+
fontFamily: 'Courier New',
|
|
20
|
+
},
|
|
21
|
+
mgap: {
|
|
22
|
+
display: 'flex',
|
|
23
|
+
flexDirection: 'column',
|
|
24
|
+
gap: theme.spacing(4),
|
|
25
|
+
},
|
|
26
|
+
}));
|
|
27
|
+
function ClusterDialog({ model, handleClose, }) {
|
|
28
|
+
const { classes } = useStyles();
|
|
29
|
+
const [results, setResults] = (0, react_1.useState)();
|
|
30
|
+
const [error, setError] = (0, react_1.useState)();
|
|
31
|
+
const [paste, setPaste] = (0, react_1.useState)('');
|
|
32
|
+
const [useCompleteMethod, setUseCompleteMethod] = (0, react_1.useState)(false);
|
|
33
|
+
(0, react_1.useEffect)(() => {
|
|
34
|
+
;
|
|
35
|
+
(async () => {
|
|
36
|
+
try {
|
|
37
|
+
setError(undefined);
|
|
38
|
+
const view = (0, util_1.getContainingView)(model);
|
|
39
|
+
if (!view.initialized) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const { rpcManager } = (0, util_1.getSession)(model);
|
|
43
|
+
const { sources, minorAlleleFrequencyFilter, adapterConfig } = model;
|
|
44
|
+
const sessionId = (0, tracks_1.getRpcSessionId)(model);
|
|
45
|
+
const { bpPerPx } = view;
|
|
46
|
+
const ret = (await rpcManager.call(sessionId, 'MultiWiggleGetScoreMatrix', {
|
|
47
|
+
regions: view.dynamicBlocks.contentBlocks,
|
|
48
|
+
sources,
|
|
49
|
+
minorAlleleFrequencyFilter,
|
|
50
|
+
sessionId,
|
|
51
|
+
adapterConfig,
|
|
52
|
+
bpPerPx,
|
|
53
|
+
}));
|
|
54
|
+
const entries = Object.values(ret);
|
|
55
|
+
const keys = Object.keys(ret);
|
|
56
|
+
const clusterMethod = useCompleteMethod ? 'complete' : 'single';
|
|
57
|
+
const text = `try(library(fastcluster), silent=TRUE)
|
|
58
|
+
inputMatrix<-matrix(c(${entries.map(val => val.scores.join(',')).join(',\n')}
|
|
59
|
+
),nrow=${entries.length},byrow=TRUE)
|
|
60
|
+
rownames(inputMatrix)<-c(${keys.map(key => `'${key}'`).join(',')})
|
|
61
|
+
resultClusters<-hclust(dist(inputMatrix), method='${clusterMethod}')
|
|
62
|
+
cat(resultClusters$order,sep='\\n')`;
|
|
63
|
+
setResults(text);
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
if (!(0, util_1.isAbortException)(e) && (0, mobx_state_tree_1.isAlive)(model)) {
|
|
67
|
+
console.error(e);
|
|
68
|
+
setError(e);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
})();
|
|
72
|
+
}, [model, useCompleteMethod]);
|
|
73
|
+
return ((0, jsx_runtime_1.jsxs)(ui_1.Dialog, { open: true, title: "Cluster by score", onClose: handleClose, children: [(0, jsx_runtime_1.jsx)(material_1.DialogContent, { children: (0, jsx_runtime_1.jsxs)("div", { className: classes.mgap, children: [(0, jsx_runtime_1.jsx)(material_1.Typography, { children: "This page will produce an R script that will perform hierarchical clustering on the visible score data using `hclust`." }), (0, jsx_runtime_1.jsx)(material_1.Typography, { children: "You can then paste the results in this form to specify the row ordering." }), results ? ((0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsxs)("div", { children: ["Step 1:", ' ', (0, jsx_runtime_1.jsx)(material_1.Button, { variant: "contained", onClick: () => {
|
|
74
|
+
(0, file_saver_1.saveAs)(new Blob([results || ''], {
|
|
75
|
+
type: 'text/plain;charset=utf-8',
|
|
76
|
+
}), 'cluster.R');
|
|
77
|
+
}, children: "Download Rscript" }), ' ', "or", ' ', (0, jsx_runtime_1.jsx)(material_1.Button, { variant: "contained", onClick: () => {
|
|
78
|
+
(0, copy_to_clipboard_1.default)(results || '');
|
|
79
|
+
}, children: "Copy Rscript to clipboard" }), (0, jsx_runtime_1.jsx)(material_1.FormControlLabel, { control: (0, jsx_runtime_1.jsx)(material_1.Checkbox, { checked: useCompleteMethod, onChange: e => {
|
|
80
|
+
setUseCompleteMethod(e.target.checked);
|
|
81
|
+
} }), label: "Use 'complete' linkage method instead of 'single'" }), (0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)(material_1.TextField, { multiline: true, fullWidth: true, variant: "outlined", placeholder: "Step 2. Paste results from Rscript here (sequence of numbers, one per line, specifying the new ordering)", rows: 10, value: paste, onChange: event => {
|
|
82
|
+
setPaste(event.target.value);
|
|
83
|
+
}, slotProps: {
|
|
84
|
+
input: {
|
|
85
|
+
classes: {
|
|
86
|
+
input: classes.textAreaFont,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
} }) })] }) })) : ((0, jsx_runtime_1.jsx)(ui_1.LoadingEllipses, { variant: "h6", title: "Generating score matrix" })), error ? (0, jsx_runtime_1.jsx)(ui_1.ErrorMessage, { error: error }) : null] }) }), (0, jsx_runtime_1.jsxs)(material_1.DialogActions, { children: [(0, jsx_runtime_1.jsx)(material_1.Button, { disabled: !results, variant: "contained", onClick: () => {
|
|
90
|
+
const { sources } = model;
|
|
91
|
+
if (sources) {
|
|
92
|
+
try {
|
|
93
|
+
model.setLayout(paste
|
|
94
|
+
.split('\n')
|
|
95
|
+
.map(t => t.trim())
|
|
96
|
+
.filter(f => !!f)
|
|
97
|
+
.map(r => +r)
|
|
98
|
+
.map(idx => {
|
|
99
|
+
const ret = sources[idx - 1];
|
|
100
|
+
if (!ret) {
|
|
101
|
+
throw new Error(`out of bounds at ${idx}`);
|
|
102
|
+
}
|
|
103
|
+
return ret;
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
106
|
+
catch (e) {
|
|
107
|
+
console.error(e);
|
|
108
|
+
setError(e);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
handleClose();
|
|
112
|
+
}, children: "Apply clustering" }), (0, jsx_runtime_1.jsx)(material_1.Button, { variant: "contained", color: "secondary", onClick: () => {
|
|
113
|
+
handleClose();
|
|
114
|
+
}, children: "Cancel" })] })] }));
|
|
115
|
+
}
|
|
@@ -50,6 +50,7 @@ const util_2 = require("../util");
|
|
|
50
50
|
const randomColor = () => '#000000'.replaceAll('0', () => (~~(Math.random() * 16)).toString(16));
|
|
51
51
|
const Tooltip = (0, react_1.lazy)(() => Promise.resolve().then(() => __importStar(require('./components/Tooltip'))));
|
|
52
52
|
const SetColorDialog = (0, react_1.lazy)(() => Promise.resolve().then(() => __importStar(require('./components/SetColorDialog'))));
|
|
53
|
+
const ClusterDialog = (0, react_1.lazy)(() => Promise.resolve().then(() => __importStar(require('./components/ClusterDialog'))));
|
|
53
54
|
const rendererTypes = new Map([
|
|
54
55
|
['xyplot', 'MultiXYPlotRenderer'],
|
|
55
56
|
['multirowxy', 'MultiRowXYPlotRenderer'],
|
|
@@ -326,12 +327,27 @@ function stateModelFactory(_pluginManager, configSchema) {
|
|
|
326
327
|
},
|
|
327
328
|
]
|
|
328
329
|
: []),
|
|
330
|
+
{
|
|
331
|
+
label: 'Cluster by score',
|
|
332
|
+
onClick: () => {
|
|
333
|
+
(0, util_1.getSession)(self).queueDialog(handleClose => [
|
|
334
|
+
ClusterDialog,
|
|
335
|
+
{
|
|
336
|
+
model: self,
|
|
337
|
+
handleClose,
|
|
338
|
+
},
|
|
339
|
+
]);
|
|
340
|
+
},
|
|
341
|
+
},
|
|
329
342
|
{
|
|
330
343
|
label: 'Edit colors/arrangement...',
|
|
331
344
|
onClick: () => {
|
|
332
345
|
(0, util_1.getSession)(self).queueDialog(handleClose => [
|
|
333
346
|
SetColorDialog,
|
|
334
|
-
{
|
|
347
|
+
{
|
|
348
|
+
model: self,
|
|
349
|
+
handleClose,
|
|
350
|
+
},
|
|
335
351
|
]);
|
|
336
352
|
},
|
|
337
353
|
},
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface Source {
|
|
2
|
+
baseUri?: string;
|
|
3
|
+
name: string;
|
|
4
|
+
label?: string;
|
|
5
|
+
color?: string;
|
|
6
|
+
group?: string;
|
|
7
|
+
HP?: number;
|
|
8
|
+
id?: string;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
export interface SampleInfo {
|
|
12
|
+
isPhased: boolean;
|
|
13
|
+
maxPloidy: number;
|
|
14
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import RpcMethodTypeWithFiltersAndRenameRegions from '@jbrowse/core/pluggableElementTypes/RpcMethodTypeWithFiltersAndRenameRegions';
|
|
2
|
+
import { type Region } from '@jbrowse/core/util';
|
|
3
|
+
import type { AnyConfigurationModel } from '@jbrowse/core/configuration';
|
|
4
|
+
export declare class MultiWiggleGetScoreMatrix extends RpcMethodTypeWithFiltersAndRenameRegions {
|
|
5
|
+
name: string;
|
|
6
|
+
execute(args: {
|
|
7
|
+
adapterConfig: AnyConfigurationModel;
|
|
8
|
+
stopToken?: string;
|
|
9
|
+
sessionId: string;
|
|
10
|
+
headers?: Record<string, string>;
|
|
11
|
+
regions: Region[];
|
|
12
|
+
bpPerPx: number;
|
|
13
|
+
}, rpcDriverClassName: string): Promise<Record<string, {
|
|
14
|
+
name: string;
|
|
15
|
+
scores: string[];
|
|
16
|
+
}>>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.MultiWiggleGetScoreMatrix = void 0;
|
|
7
|
+
const dataAdapterCache_1 = require("@jbrowse/core/data_adapters/dataAdapterCache");
|
|
8
|
+
const RpcMethodTypeWithFiltersAndRenameRegions_1 = __importDefault(require("@jbrowse/core/pluggableElementTypes/RpcMethodTypeWithFiltersAndRenameRegions"));
|
|
9
|
+
const util_1 = require("@jbrowse/core/util");
|
|
10
|
+
const rxjs_1 = require("rxjs");
|
|
11
|
+
class MultiWiggleGetScoreMatrix extends RpcMethodTypeWithFiltersAndRenameRegions_1.default {
|
|
12
|
+
constructor() {
|
|
13
|
+
super(...arguments);
|
|
14
|
+
this.name = 'MultiWiggleGetScoreMatrix';
|
|
15
|
+
}
|
|
16
|
+
async execute(args, rpcDriverClassName) {
|
|
17
|
+
var _a, _b;
|
|
18
|
+
const pm = this.pluginManager;
|
|
19
|
+
const deserializedArgs = await this.deserializeArguments(args, rpcDriverClassName);
|
|
20
|
+
const { sources, regions, adapterConfig, sessionId, bpPerPx } = deserializedArgs;
|
|
21
|
+
const adapter = await (0, dataAdapterCache_1.getAdapter)(pm, sessionId, adapterConfig);
|
|
22
|
+
const dataAdapter = adapter.dataAdapter;
|
|
23
|
+
const resolution = 2;
|
|
24
|
+
const bpScale = bpPerPx / resolution;
|
|
25
|
+
const r0 = regions[0];
|
|
26
|
+
const r0len = r0.end - r0.start;
|
|
27
|
+
const w = Math.floor(r0len / bpScale);
|
|
28
|
+
const feats = await (0, rxjs_1.firstValueFrom)(dataAdapter.getFeatures(r0, deserializedArgs).pipe((0, rxjs_1.toArray)()));
|
|
29
|
+
const groups = (0, util_1.groupBy)(feats, f => f.get('source'));
|
|
30
|
+
const rows = {};
|
|
31
|
+
for (const source of sources) {
|
|
32
|
+
const { name } = source;
|
|
33
|
+
const features = groups[name] || [];
|
|
34
|
+
for (const feat of features) {
|
|
35
|
+
if (!rows[name]) {
|
|
36
|
+
rows[name] = {
|
|
37
|
+
name,
|
|
38
|
+
scores: new Array(w),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const fstart = feat.get('start');
|
|
42
|
+
const fend = feat.get('end');
|
|
43
|
+
const score = feat.get('score');
|
|
44
|
+
for (let i = fstart; i < fend; i += bpScale) {
|
|
45
|
+
if (i > r0.start && i < r0.end) {
|
|
46
|
+
(_a = rows[name].scores)[_b = Math.floor((i - r0.start) / bpScale)] || (_a[_b] = score);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return rows;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
exports.MultiWiggleGetScoreMatrix = MultiWiggleGetScoreMatrix;
|
|
@@ -17,3 +17,4 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
__exportStar(require("./MultiWiggleGetSources"), exports);
|
|
18
18
|
__exportStar(require("./WiggleGetMultiRegionQuantitativeStats"), exports);
|
|
19
19
|
__exportStar(require("./WiggleGetGlobalQuantitativeStats"), exports);
|
|
20
|
+
__exportStar(require("./MultiWiggleGetScoreMatrix"), exports);
|
package/dist/drawXY.js
CHANGED
|
@@ -9,12 +9,18 @@ const util_2 = require("./util");
|
|
|
9
9
|
function lighten(color, amount) {
|
|
10
10
|
const hslColor = color.toHsl();
|
|
11
11
|
const l = hslColor.l * (1 + amount);
|
|
12
|
-
return (0, colord_1.colord)({
|
|
12
|
+
return (0, colord_1.colord)({
|
|
13
|
+
...hslColor,
|
|
14
|
+
l: (0, util_1.clamp)(l, 0, 100),
|
|
15
|
+
});
|
|
13
16
|
}
|
|
14
17
|
function darken(color, amount) {
|
|
15
18
|
const hslColor = color.toHsl();
|
|
16
19
|
const l = hslColor.l * (1 - amount);
|
|
17
|
-
return (0, colord_1.colord)({
|
|
20
|
+
return (0, colord_1.colord)({
|
|
21
|
+
...hslColor,
|
|
22
|
+
l: (0, util_1.clamp)(l, 0, 100),
|
|
23
|
+
});
|
|
18
24
|
}
|
|
19
25
|
const fudgeFactor = 0.3;
|
|
20
26
|
const clipHeight = 2;
|
package/dist/index.js
CHANGED
|
@@ -96,6 +96,7 @@ class WigglePlugin extends Plugin_1.default {
|
|
|
96
96
|
pm.addRpcMethod(() => new rpcMethods_1.WiggleGetGlobalQuantitativeStats(pm));
|
|
97
97
|
pm.addRpcMethod(() => new rpcMethods_1.WiggleGetMultiRegionQuantitativeStats(pm));
|
|
98
98
|
pm.addRpcMethod(() => new rpcMethods_1.MultiWiggleGetSources(pm));
|
|
99
|
+
pm.addRpcMethod(() => new rpcMethods_1.MultiWiggleGetScoreMatrix(pm));
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
exports.default = WigglePlugin;
|
|
@@ -18,5 +18,18 @@ const BigWigAdapter = ConfigurationSchema('BigWigAdapter', {
|
|
|
18
18
|
defaultValue: 1,
|
|
19
19
|
description: 'Initial resolution multiplier',
|
|
20
20
|
},
|
|
21
|
-
}, {
|
|
21
|
+
}, {
|
|
22
|
+
explicitlyTyped: true,
|
|
23
|
+
preProcessSnapshot: snap => {
|
|
24
|
+
return snap.uri
|
|
25
|
+
? {
|
|
26
|
+
...snap,
|
|
27
|
+
bigWigLocation: {
|
|
28
|
+
uri: snap.uri,
|
|
29
|
+
baseUri: snap.baseUri,
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
: snap;
|
|
33
|
+
},
|
|
34
|
+
});
|
|
22
35
|
export default BigWigAdapter;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Source } from '../types';
|
|
2
|
+
import type { AnyConfigurationModel } from '@jbrowse/core/configuration';
|
|
3
|
+
export default function ClusterDialog({ model, handleClose, }: {
|
|
4
|
+
model: {
|
|
5
|
+
sources?: Source[];
|
|
6
|
+
minorAlleleFrequencyFilter?: number;
|
|
7
|
+
adapterConfig: AnyConfigurationModel;
|
|
8
|
+
setLayout: (arg: Source[]) => void;
|
|
9
|
+
};
|
|
10
|
+
handleClose: () => void;
|
|
11
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { Dialog, ErrorMessage, LoadingEllipses } from '@jbrowse/core/ui';
|
|
4
|
+
import { getContainingView, getSession, isAbortException, } from '@jbrowse/core/util';
|
|
5
|
+
import { getRpcSessionId } from '@jbrowse/core/util/tracks';
|
|
6
|
+
import { Button, Checkbox, DialogActions, DialogContent, FormControlLabel, TextField, Typography, } from '@mui/material';
|
|
7
|
+
import copy from 'copy-to-clipboard';
|
|
8
|
+
import { saveAs } from 'file-saver';
|
|
9
|
+
import { isAlive } from 'mobx-state-tree';
|
|
10
|
+
import { makeStyles } from 'tss-react/mui';
|
|
11
|
+
const useStyles = makeStyles()(theme => ({
|
|
12
|
+
textAreaFont: {
|
|
13
|
+
fontFamily: 'Courier New',
|
|
14
|
+
},
|
|
15
|
+
mgap: {
|
|
16
|
+
display: 'flex',
|
|
17
|
+
flexDirection: 'column',
|
|
18
|
+
gap: theme.spacing(4),
|
|
19
|
+
},
|
|
20
|
+
}));
|
|
21
|
+
export default function ClusterDialog({ model, handleClose, }) {
|
|
22
|
+
const { classes } = useStyles();
|
|
23
|
+
const [results, setResults] = useState();
|
|
24
|
+
const [error, setError] = useState();
|
|
25
|
+
const [paste, setPaste] = useState('');
|
|
26
|
+
const [useCompleteMethod, setUseCompleteMethod] = useState(false);
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
;
|
|
29
|
+
(async () => {
|
|
30
|
+
try {
|
|
31
|
+
setError(undefined);
|
|
32
|
+
const view = getContainingView(model);
|
|
33
|
+
if (!view.initialized) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const { rpcManager } = getSession(model);
|
|
37
|
+
const { sources, minorAlleleFrequencyFilter, adapterConfig } = model;
|
|
38
|
+
const sessionId = getRpcSessionId(model);
|
|
39
|
+
const { bpPerPx } = view;
|
|
40
|
+
const ret = (await rpcManager.call(sessionId, 'MultiWiggleGetScoreMatrix', {
|
|
41
|
+
regions: view.dynamicBlocks.contentBlocks,
|
|
42
|
+
sources,
|
|
43
|
+
minorAlleleFrequencyFilter,
|
|
44
|
+
sessionId,
|
|
45
|
+
adapterConfig,
|
|
46
|
+
bpPerPx,
|
|
47
|
+
}));
|
|
48
|
+
const entries = Object.values(ret);
|
|
49
|
+
const keys = Object.keys(ret);
|
|
50
|
+
const clusterMethod = useCompleteMethod ? 'complete' : 'single';
|
|
51
|
+
const text = `try(library(fastcluster), silent=TRUE)
|
|
52
|
+
inputMatrix<-matrix(c(${entries.map(val => val.scores.join(',')).join(',\n')}
|
|
53
|
+
),nrow=${entries.length},byrow=TRUE)
|
|
54
|
+
rownames(inputMatrix)<-c(${keys.map(key => `'${key}'`).join(',')})
|
|
55
|
+
resultClusters<-hclust(dist(inputMatrix), method='${clusterMethod}')
|
|
56
|
+
cat(resultClusters$order,sep='\\n')`;
|
|
57
|
+
setResults(text);
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
if (!isAbortException(e) && isAlive(model)) {
|
|
61
|
+
console.error(e);
|
|
62
|
+
setError(e);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
})();
|
|
66
|
+
}, [model, useCompleteMethod]);
|
|
67
|
+
return (_jsxs(Dialog, { open: true, title: "Cluster by score", onClose: handleClose, children: [_jsx(DialogContent, { children: _jsxs("div", { className: classes.mgap, children: [_jsx(Typography, { children: "This page will produce an R script that will perform hierarchical clustering on the visible score data using `hclust`." }), _jsx(Typography, { children: "You can then paste the results in this form to specify the row ordering." }), results ? (_jsx("div", { children: _jsxs("div", { children: ["Step 1:", ' ', _jsx(Button, { variant: "contained", onClick: () => {
|
|
68
|
+
saveAs(new Blob([results || ''], {
|
|
69
|
+
type: 'text/plain;charset=utf-8',
|
|
70
|
+
}), 'cluster.R');
|
|
71
|
+
}, children: "Download Rscript" }), ' ', "or", ' ', _jsx(Button, { variant: "contained", onClick: () => {
|
|
72
|
+
copy(results || '');
|
|
73
|
+
}, children: "Copy Rscript to clipboard" }), _jsx(FormControlLabel, { control: _jsx(Checkbox, { checked: useCompleteMethod, onChange: e => {
|
|
74
|
+
setUseCompleteMethod(e.target.checked);
|
|
75
|
+
} }), label: "Use 'complete' linkage method instead of 'single'" }), _jsx("div", { children: _jsx(TextField, { multiline: true, fullWidth: true, variant: "outlined", placeholder: "Step 2. Paste results from Rscript here (sequence of numbers, one per line, specifying the new ordering)", rows: 10, value: paste, onChange: event => {
|
|
76
|
+
setPaste(event.target.value);
|
|
77
|
+
}, slotProps: {
|
|
78
|
+
input: {
|
|
79
|
+
classes: {
|
|
80
|
+
input: classes.textAreaFont,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
} }) })] }) })) : (_jsx(LoadingEllipses, { variant: "h6", title: "Generating score matrix" })), error ? _jsx(ErrorMessage, { error: error }) : null] }) }), _jsxs(DialogActions, { children: [_jsx(Button, { disabled: !results, variant: "contained", onClick: () => {
|
|
84
|
+
const { sources } = model;
|
|
85
|
+
if (sources) {
|
|
86
|
+
try {
|
|
87
|
+
model.setLayout(paste
|
|
88
|
+
.split('\n')
|
|
89
|
+
.map(t => t.trim())
|
|
90
|
+
.filter(f => !!f)
|
|
91
|
+
.map(r => +r)
|
|
92
|
+
.map(idx => {
|
|
93
|
+
const ret = sources[idx - 1];
|
|
94
|
+
if (!ret) {
|
|
95
|
+
throw new Error(`out of bounds at ${idx}`);
|
|
96
|
+
}
|
|
97
|
+
return ret;
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
console.error(e);
|
|
102
|
+
setError(e);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
handleClose();
|
|
106
|
+
}, children: "Apply clustering" }), _jsx(Button, { variant: "contained", color: "secondary", onClick: () => {
|
|
107
|
+
handleClose();
|
|
108
|
+
}, children: "Cancel" })] })] }));
|
|
109
|
+
}
|
|
@@ -11,6 +11,7 @@ import { YSCALEBAR_LABEL_OFFSET, getScale } from '../util';
|
|
|
11
11
|
const randomColor = () => '#000000'.replaceAll('0', () => (~~(Math.random() * 16)).toString(16));
|
|
12
12
|
const Tooltip = lazy(() => import('./components/Tooltip'));
|
|
13
13
|
const SetColorDialog = lazy(() => import('./components/SetColorDialog'));
|
|
14
|
+
const ClusterDialog = lazy(() => import('./components/ClusterDialog'));
|
|
14
15
|
const rendererTypes = new Map([
|
|
15
16
|
['xyplot', 'MultiXYPlotRenderer'],
|
|
16
17
|
['multirowxy', 'MultiRowXYPlotRenderer'],
|
|
@@ -287,12 +288,27 @@ export function stateModelFactory(_pluginManager, configSchema) {
|
|
|
287
288
|
},
|
|
288
289
|
]
|
|
289
290
|
: []),
|
|
291
|
+
{
|
|
292
|
+
label: 'Cluster by score',
|
|
293
|
+
onClick: () => {
|
|
294
|
+
getSession(self).queueDialog(handleClose => [
|
|
295
|
+
ClusterDialog,
|
|
296
|
+
{
|
|
297
|
+
model: self,
|
|
298
|
+
handleClose,
|
|
299
|
+
},
|
|
300
|
+
]);
|
|
301
|
+
},
|
|
302
|
+
},
|
|
290
303
|
{
|
|
291
304
|
label: 'Edit colors/arrangement...',
|
|
292
305
|
onClick: () => {
|
|
293
306
|
getSession(self).queueDialog(handleClose => [
|
|
294
307
|
SetColorDialog,
|
|
295
|
-
{
|
|
308
|
+
{
|
|
309
|
+
model: self,
|
|
310
|
+
handleClose,
|
|
311
|
+
},
|
|
296
312
|
]);
|
|
297
313
|
},
|
|
298
314
|
},
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface Source {
|
|
2
|
+
baseUri?: string;
|
|
3
|
+
name: string;
|
|
4
|
+
label?: string;
|
|
5
|
+
color?: string;
|
|
6
|
+
group?: string;
|
|
7
|
+
HP?: number;
|
|
8
|
+
id?: string;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
export interface SampleInfo {
|
|
12
|
+
isPhased: boolean;
|
|
13
|
+
maxPloidy: number;
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import RpcMethodTypeWithFiltersAndRenameRegions from '@jbrowse/core/pluggableElementTypes/RpcMethodTypeWithFiltersAndRenameRegions';
|
|
2
|
+
import { type Region } from '@jbrowse/core/util';
|
|
3
|
+
import type { AnyConfigurationModel } from '@jbrowse/core/configuration';
|
|
4
|
+
export declare class MultiWiggleGetScoreMatrix extends RpcMethodTypeWithFiltersAndRenameRegions {
|
|
5
|
+
name: string;
|
|
6
|
+
execute(args: {
|
|
7
|
+
adapterConfig: AnyConfigurationModel;
|
|
8
|
+
stopToken?: string;
|
|
9
|
+
sessionId: string;
|
|
10
|
+
headers?: Record<string, string>;
|
|
11
|
+
regions: Region[];
|
|
12
|
+
bpPerPx: number;
|
|
13
|
+
}, rpcDriverClassName: string): Promise<Record<string, {
|
|
14
|
+
name: string;
|
|
15
|
+
scores: string[];
|
|
16
|
+
}>>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { getAdapter } from '@jbrowse/core/data_adapters/dataAdapterCache';
|
|
2
|
+
import RpcMethodTypeWithFiltersAndRenameRegions from '@jbrowse/core/pluggableElementTypes/RpcMethodTypeWithFiltersAndRenameRegions';
|
|
3
|
+
import { groupBy } from '@jbrowse/core/util';
|
|
4
|
+
import { firstValueFrom, toArray } from 'rxjs';
|
|
5
|
+
export class MultiWiggleGetScoreMatrix extends RpcMethodTypeWithFiltersAndRenameRegions {
|
|
6
|
+
constructor() {
|
|
7
|
+
super(...arguments);
|
|
8
|
+
this.name = 'MultiWiggleGetScoreMatrix';
|
|
9
|
+
}
|
|
10
|
+
async execute(args, rpcDriverClassName) {
|
|
11
|
+
var _a, _b;
|
|
12
|
+
const pm = this.pluginManager;
|
|
13
|
+
const deserializedArgs = await this.deserializeArguments(args, rpcDriverClassName);
|
|
14
|
+
const { sources, regions, adapterConfig, sessionId, bpPerPx } = deserializedArgs;
|
|
15
|
+
const adapter = await getAdapter(pm, sessionId, adapterConfig);
|
|
16
|
+
const dataAdapter = adapter.dataAdapter;
|
|
17
|
+
const resolution = 2;
|
|
18
|
+
const bpScale = bpPerPx / resolution;
|
|
19
|
+
const r0 = regions[0];
|
|
20
|
+
const r0len = r0.end - r0.start;
|
|
21
|
+
const w = Math.floor(r0len / bpScale);
|
|
22
|
+
const feats = await firstValueFrom(dataAdapter.getFeatures(r0, deserializedArgs).pipe(toArray()));
|
|
23
|
+
const groups = groupBy(feats, f => f.get('source'));
|
|
24
|
+
const rows = {};
|
|
25
|
+
for (const source of sources) {
|
|
26
|
+
const { name } = source;
|
|
27
|
+
const features = groups[name] || [];
|
|
28
|
+
for (const feat of features) {
|
|
29
|
+
if (!rows[name]) {
|
|
30
|
+
rows[name] = {
|
|
31
|
+
name,
|
|
32
|
+
scores: new Array(w),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const fstart = feat.get('start');
|
|
36
|
+
const fend = feat.get('end');
|
|
37
|
+
const score = feat.get('score');
|
|
38
|
+
for (let i = fstart; i < fend; i += bpScale) {
|
|
39
|
+
if (i > r0.start && i < r0.end) {
|
|
40
|
+
(_a = rows[name].scores)[_b = Math.floor((i - r0.start) / bpScale)] || (_a[_b] = score);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return rows;
|
|
46
|
+
}
|
|
47
|
+
}
|
package/esm/drawXY.js
CHANGED
|
@@ -6,12 +6,18 @@ import { fillRectCtx, getOrigin, getScale } from './util';
|
|
|
6
6
|
function lighten(color, amount) {
|
|
7
7
|
const hslColor = color.toHsl();
|
|
8
8
|
const l = hslColor.l * (1 + amount);
|
|
9
|
-
return colord({
|
|
9
|
+
return colord({
|
|
10
|
+
...hslColor,
|
|
11
|
+
l: clamp(l, 0, 100),
|
|
12
|
+
});
|
|
10
13
|
}
|
|
11
14
|
function darken(color, amount) {
|
|
12
15
|
const hslColor = color.toHsl();
|
|
13
16
|
const l = hslColor.l * (1 - amount);
|
|
14
|
-
return colord({
|
|
17
|
+
return colord({
|
|
18
|
+
...hslColor,
|
|
19
|
+
l: clamp(l, 0, 100),
|
|
20
|
+
});
|
|
15
21
|
}
|
|
16
22
|
const fudgeFactor = 0.3;
|
|
17
23
|
const clipHeight = 2;
|
package/esm/index.js
CHANGED
|
@@ -16,7 +16,7 @@ import MultiWiggleAddTrackWorkflowF from './MultiWiggleAddTrackWorkflow';
|
|
|
16
16
|
import MultiXYPlotRendererF from './MultiXYPlotRenderer';
|
|
17
17
|
import QuantitativeTrackF from './QuantitativeTrack';
|
|
18
18
|
import WiggleBaseRenderer from './WiggleBaseRenderer';
|
|
19
|
-
import { MultiWiggleGetSources, WiggleGetGlobalQuantitativeStats, WiggleGetMultiRegionQuantitativeStats, } from './WiggleRPC/rpcMethods';
|
|
19
|
+
import { MultiWiggleGetScoreMatrix, MultiWiggleGetSources, WiggleGetGlobalQuantitativeStats, WiggleGetMultiRegionQuantitativeStats, } from './WiggleRPC/rpcMethods';
|
|
20
20
|
import XYPlotRendererF, { ReactComponent as XYPlotRendererReactComponent, XYPlotRenderer, configSchema as xyPlotRendererConfigSchema, } from './XYPlotRenderer';
|
|
21
21
|
import * as utils from './util';
|
|
22
22
|
export default class WigglePlugin extends Plugin {
|
|
@@ -54,6 +54,7 @@ export default class WigglePlugin extends Plugin {
|
|
|
54
54
|
pm.addRpcMethod(() => new WiggleGetGlobalQuantitativeStats(pm));
|
|
55
55
|
pm.addRpcMethod(() => new WiggleGetMultiRegionQuantitativeStats(pm));
|
|
56
56
|
pm.addRpcMethod(() => new MultiWiggleGetSources(pm));
|
|
57
|
+
pm.addRpcMethod(() => new MultiWiggleGetScoreMatrix(pm));
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
60
|
export * from './util';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jbrowse/plugin-wiggle",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "JBrowse 2 wiggle adapters, tracks, etc.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jbrowse",
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@gmod/bbi": "^6.0.0",
|
|
40
|
-
"@jbrowse/core": "^3.0
|
|
41
|
-
"@jbrowse/plugin-data-management": "^3.0
|
|
42
|
-
"@jbrowse/plugin-linear-genome-view": "^3.0
|
|
40
|
+
"@jbrowse/core": "^3.1.0",
|
|
41
|
+
"@jbrowse/plugin-data-management": "^3.1.0",
|
|
42
|
+
"@jbrowse/plugin-linear-genome-view": "^3.1.0",
|
|
43
43
|
"@mui/icons-material": "^6.0.0",
|
|
44
44
|
"@mui/material": "^6.0.0",
|
|
45
45
|
"@mui/x-charts-vendor": "^7.12.0",
|
|
@@ -62,5 +62,5 @@
|
|
|
62
62
|
"distModule": "esm/index.js",
|
|
63
63
|
"srcModule": "src/index.ts",
|
|
64
64
|
"module": "esm/index.js",
|
|
65
|
-
"gitHead": "
|
|
65
|
+
"gitHead": "91492049ddea0aed90eb24d3c066c2d9f5a6b189"
|
|
66
66
|
}
|