@datagrok/bio 2.1.11 → 2.4.2
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/README.md +11 -12
- package/css/helm.css +10 -0
- package/detectors.js +83 -59
- package/dist/package-test.js +2 -68651
- package/dist/package-test.js.map +1 -0
- package/dist/package.js +2 -66040
- package/dist/package.js.map +1 -0
- package/dockerfiles/Dockerfile +86 -0
- package/files/icons/composition-analysis.svg +17 -0
- package/files/icons/sequence-diversity-viewer.svg +4 -0
- package/files/icons/sequence-similarity-viewer.svg +4 -0
- package/files/icons/vdregions-viewer.svg +22 -0
- package/files/icons/weblogo-viewer.svg +7 -0
- package/files/tests/testUrl.csv +11 -0
- package/files/tests/toAtomicLevelTest.csv +4 -0
- package/package.json +29 -32
- package/src/analysis/sequence-activity-cliffs.ts +15 -13
- package/src/analysis/sequence-diversity-viewer.ts +3 -2
- package/src/analysis/sequence-search-base-viewer.ts +4 -2
- package/src/analysis/sequence-similarity-viewer.ts +4 -4
- package/src/analysis/sequence-space.ts +2 -1
- package/src/calculations/monomerLevelMols.ts +6 -6
- package/src/package-test.ts +9 -2
- package/src/package.ts +230 -145
- package/src/substructure-search/substructure-search.ts +25 -22
- package/src/tests/Palettes-test.ts +9 -9
- package/src/tests/WebLogo-positions-test.ts +131 -68
- package/src/tests/_first-tests.ts +9 -0
- package/src/tests/activity-cliffs-tests.ts +8 -7
- package/src/tests/activity-cliffs-utils.ts +17 -9
- package/src/tests/bio-tests.ts +30 -21
- package/src/tests/checkInputColumn-tests.ts +17 -17
- package/src/tests/converters-test.ts +81 -46
- package/src/tests/detectors-benchmark-tests.ts +17 -17
- package/src/tests/detectors-tests.ts +190 -178
- package/src/tests/fasta-export-tests.ts +2 -3
- package/src/tests/monomer-libraries-tests.ts +34 -0
- package/src/tests/pepsea-tests.ts +21 -0
- package/src/tests/renderers-test.ts +33 -29
- package/src/tests/sequence-space-test.ts +6 -4
- package/src/tests/similarity-diversity-tests.ts +4 -4
- package/src/tests/splitters-test.ts +6 -7
- package/src/tests/substructure-filters-tests.ts +23 -1
- package/src/tests/utils/sequences-generators.ts +7 -7
- package/src/tests/utils.ts +2 -1
- package/src/tests/viewers.ts +16 -0
- package/src/utils/cell-renderer.ts +116 -54
- package/src/utils/constants.ts +7 -6
- package/src/utils/convert.ts +17 -11
- package/src/utils/monomer-lib.ts +174 -0
- package/src/utils/multiple-sequence-alignment.ts +49 -26
- package/src/utils/pepsea.ts +78 -0
- package/src/utils/save-as-fasta.ts +9 -8
- package/src/utils/ui-utils.ts +15 -3
- package/src/viewers/vd-regions-viewer.ts +125 -83
- package/src/viewers/web-logo-viewer.ts +1031 -0
- package/src/widgets/bio-substructure-filter.ts +38 -24
- package/tsconfig.json +71 -72
- package/webpack.config.js +4 -11
- package/dist/vendors-node_modules_datagrok-libraries_ml_src_workers_dimensionality-reducer_js.js +0 -8988
- package/jest.config.js +0 -33
- package/src/__jest__/remote.test.ts +0 -77
- package/src/__jest__/test-node.ts +0 -98
- package/test-Bio-91c83d8913ff-bb573307.html +0 -392
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
/* Do not change these import lines to match external modules in webpack configuration */
|
|
2
|
-
import * as grok from 'datagrok-api/grok';
|
|
3
|
-
import * as ui from 'datagrok-api/ui';
|
|
4
2
|
import * as DG from 'datagrok-api/dg';
|
|
5
|
-
import * as bio from '@datagrok-libraries/bio';
|
|
6
3
|
|
|
7
4
|
import {FastaFileHandler} from '@datagrok-libraries/bio/src/utils/fasta-handler';
|
|
8
|
-
|
|
9
|
-
//@ts-ignore
|
|
5
|
+
import {TAGS as bioTAGS} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
6
|
+
//@ts-ignore: there are no types for this library
|
|
10
7
|
import Aioli from '@biowasm/aioli';
|
|
11
8
|
|
|
12
9
|
import {AlignedSequenceEncoder} from '@datagrok-libraries/bio/src/sequence-encoder';
|
|
13
|
-
|
|
10
|
+
const fastaInputFilename = 'input.fa';
|
|
11
|
+
const fastaOutputFilename = 'result.fasta';
|
|
14
12
|
|
|
15
13
|
/**
|
|
16
14
|
* Converts array of sequences into simple fasta string.
|
|
@@ -30,48 +28,73 @@ function _stringsToFasta(sequences: string[]): string {
|
|
|
30
28
|
* @param {string} unUsedName
|
|
31
29
|
* @return {Promise<DG.Column>} Aligned sequences.
|
|
32
30
|
*/
|
|
33
|
-
export async function runKalign(srcCol: DG.Column
|
|
34
|
-
|
|
31
|
+
export async function runKalign(srcCol: DG.Column<string>, isAligned: boolean = false, unUsedName: string = '',
|
|
32
|
+
clustersCol: DG.Column | null = null): Promise<DG.Column> {
|
|
33
|
+
let sequences: string[] = srcCol.toList();
|
|
35
34
|
|
|
36
35
|
if (isAligned)
|
|
37
|
-
sequences = sequences.map((v: string
|
|
36
|
+
sequences = sequences.map((v: string) => AlignedSequenceEncoder.clean(v).replace(/\-/g, ''));
|
|
37
|
+
|
|
38
|
+
const sequencesLength = srcCol.length;
|
|
39
|
+
clustersCol ??= DG.Column.string('Clusters', sequencesLength).init('0');
|
|
40
|
+
if (clustersCol.type != DG.COLUMN_TYPE.STRING)
|
|
41
|
+
clustersCol = clustersCol.convertTo(DG.TYPE.STRING);
|
|
42
|
+
clustersCol.compact();
|
|
43
|
+
|
|
44
|
+
//TODO: use fixed-size inner arrays, but first need to expose the method to get each category count
|
|
45
|
+
const clustersColCategories = clustersCol.categories;
|
|
46
|
+
const clustersColData = clustersCol.getRawData();
|
|
47
|
+
const fastaSequences: string[][] = new Array(clustersColCategories.length);
|
|
48
|
+
const clusterIndexes: number[][] = new Array(clustersColCategories.length);
|
|
49
|
+
for (let rowIdx = 0; rowIdx < sequencesLength; ++rowIdx) {
|
|
50
|
+
const clusterCategoryIdx = clustersColData[rowIdx];
|
|
51
|
+
(fastaSequences[clusterCategoryIdx] ??= []).push(sequences[rowIdx]);
|
|
52
|
+
(clusterIndexes[clusterCategoryIdx] ??= []).push(rowIdx);
|
|
53
|
+
}
|
|
38
54
|
|
|
39
|
-
const fasta = _stringsToFasta(sequences);
|
|
40
55
|
const CLI = await new Aioli([
|
|
41
56
|
'base/1.0.0',
|
|
42
|
-
{tool: 'kalign', version: '3.3.1', reinit: true
|
|
57
|
+
{tool: 'kalign', version: '3.3.1', reinit: true}
|
|
43
58
|
]);
|
|
59
|
+
const tgtCol = DG.Column.string(unUsedName, sequencesLength);
|
|
44
60
|
|
|
45
|
-
|
|
61
|
+
for (let clusterIdx = 0; clusterIdx < clustersColCategories.length; ++clusterIdx) {
|
|
62
|
+
const clusterSequences = fastaSequences[clusterIdx];
|
|
63
|
+
const fasta = _stringsToFasta(clusterSequences);
|
|
64
|
+
|
|
65
|
+
console.log(['fasta.length =', fasta.length]);
|
|
46
66
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
67
|
+
await CLI.fs.writeFile(fastaInputFilename, fasta);
|
|
68
|
+
const output = await CLI.exec(`kalign ${fastaInputFilename} -f fasta -o ${fastaOutputFilename}`);
|
|
69
|
+
console.warn(output);
|
|
50
70
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
71
|
+
const buf = await CLI.cat(fastaOutputFilename);
|
|
72
|
+
if (!buf)
|
|
73
|
+
throw new Error(`kalign output no result`);
|
|
54
74
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
75
|
+
const ffh = new FastaFileHandler(buf);
|
|
76
|
+
const aligned = ffh.sequencesArray; // array of sequences extracted from FASTA
|
|
77
|
+
const clusterRowIds = clusterIndexes[clusterIdx];
|
|
78
|
+
for (let clusterRowIdIdx = 0; clusterRowIdIdx < aligned.length; ++clusterRowIdIdx)
|
|
79
|
+
tgtCol.set(clusterRowIds[clusterRowIdIdx], aligned[clusterRowIdIdx]);
|
|
80
|
+
}
|
|
58
81
|
|
|
59
82
|
// units
|
|
60
83
|
const srcUnits = srcCol.getTag(DG.TAGS.UNITS);
|
|
61
84
|
//aligned
|
|
62
|
-
const srcAligned = srcCol.getTag(
|
|
85
|
+
const srcAligned = srcCol.getTag(bioTAGS.aligned);
|
|
63
86
|
const tgtAligned = srcAligned + '.MSA';
|
|
64
87
|
//alphabet
|
|
65
|
-
const srcAlphabet = srcCol.getTag(
|
|
88
|
+
const srcAlphabet = srcCol.getTag(bioTAGS.alphabet);
|
|
66
89
|
|
|
67
90
|
tgtCol.setTag(DG.TAGS.UNITS, srcUnits);
|
|
68
|
-
tgtCol.setTag(
|
|
69
|
-
tgtCol.setTag(
|
|
91
|
+
tgtCol.setTag(bioTAGS.aligned, tgtAligned);
|
|
92
|
+
tgtCol.setTag(bioTAGS.alphabet, srcAlphabet);
|
|
70
93
|
tgtCol.semType = DG.SEMTYPE.MACROMOLECULE;
|
|
71
94
|
return tgtCol;
|
|
72
95
|
}
|
|
73
96
|
|
|
74
|
-
export async function testMSAEnoughMemory(col: DG.Column): Promise<void> {
|
|
97
|
+
export async function testMSAEnoughMemory(col: DG.Column<string>): Promise<void> {
|
|
75
98
|
const sequencesCount = col.length;
|
|
76
99
|
const delta = sequencesCount / 100;
|
|
77
100
|
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/* Do not change these import lines to match external modules in webpack configuration */
|
|
2
|
+
import * as grok from 'datagrok-api/grok';
|
|
3
|
+
import * as DG from 'datagrok-api/dg';
|
|
4
|
+
import {NOTATION, TAGS as bioTAGS, ALIGNMENT, ALPHABET} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
5
|
+
import * as C from './constants';
|
|
6
|
+
|
|
7
|
+
export const pepseaMethods = ['mafft --auto', 'mafft', 'linsi', 'ginsi', 'einsi', 'fftns', 'fftnsi', 'nwns', 'nwnsi'];
|
|
8
|
+
const alignmentObjectMetaKeys = ['AlignedSeq', 'AlignedSubpeptide', 'HELM', 'ID', 'PolymerID'];
|
|
9
|
+
type PepseaRepsonse = {
|
|
10
|
+
Alignment: {
|
|
11
|
+
PolymerID: string, AlignedSubpeptide: string, HELM: string, ID: string, AlignedSeq: string, [key: string]: string,
|
|
12
|
+
}[],
|
|
13
|
+
AlignmentScore: {[key: string]: number | null},
|
|
14
|
+
};
|
|
15
|
+
type PepseaBodyUnit = {ID: string, HELM: string};
|
|
16
|
+
|
|
17
|
+
export async function runPepsea(srcCol: DG.Column<string>, unUsedName: string,
|
|
18
|
+
method: typeof pepseaMethods[number] = 'ginsi', gapOpen: number = 1.53, gapExtend: number = 0.0,
|
|
19
|
+
clustersCol: DG.Column<string | number> | null = null,
|
|
20
|
+
): Promise<DG.Column<string>> {
|
|
21
|
+
const peptideCount = srcCol.length;
|
|
22
|
+
clustersCol ??= DG.Column.int('Clusters', peptideCount).init(0);
|
|
23
|
+
if (clustersCol.type != DG.COLUMN_TYPE.STRING)
|
|
24
|
+
clustersCol = clustersCol.convertTo(DG.TYPE.STRING);
|
|
25
|
+
|
|
26
|
+
const clusters = clustersCol.categories;
|
|
27
|
+
const bodies: PepseaBodyUnit[][] = new Array(clusters.length);
|
|
28
|
+
|
|
29
|
+
// Grouping data by clusters
|
|
30
|
+
for (let rowIndex = 0; rowIndex < peptideCount; ++rowIndex) {
|
|
31
|
+
const cluster = clustersCol.get(rowIndex) as string;
|
|
32
|
+
if (cluster === '')
|
|
33
|
+
continue;
|
|
34
|
+
|
|
35
|
+
const clusterId = clusters.indexOf(cluster);
|
|
36
|
+
const helmSeq = srcCol.get(rowIndex);
|
|
37
|
+
if (helmSeq)
|
|
38
|
+
(bodies[clusterId] ??= []).push({ID: rowIndex.toString(), HELM: helmSeq});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
//@ts-ignore: this is a temporary workaround for the issue with docker containers. This will be fixed in 1.14.0
|
|
42
|
+
const pepseaContainer = await (grok.dapi.docker !== undefined ? grok.dapi.docker.dockerContainers : grok.dapi.dockerfiles).filter('bio').first();
|
|
43
|
+
const alignedSequences: string[] = new Array(peptideCount);
|
|
44
|
+
for (const body of bodies) { // getting aligned sequences for each cluster
|
|
45
|
+
const alignedObject = await requestAlignedObjects(pepseaContainer.id, body, method, gapOpen, gapExtend);
|
|
46
|
+
const alignments = alignedObject.Alignment;
|
|
47
|
+
|
|
48
|
+
for (const alignment of alignments) { // filling alignedSequencesCol
|
|
49
|
+
alignedSequences[parseInt(alignment.ID)] = Object.entries(alignment)
|
|
50
|
+
.filter((v) => !alignmentObjectMetaKeys.includes(v[0]))
|
|
51
|
+
.map((v) => v[1] !== '-' ? v[1] : '')
|
|
52
|
+
.join(C.PEPSEA.SEPARATOR);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const alignedSequencesCol: DG.Column<string> = DG.Column.fromStrings(unUsedName, alignedSequences);
|
|
57
|
+
alignedSequencesCol.setTag(DG.TAGS.UNITS, NOTATION.SEPARATOR);
|
|
58
|
+
alignedSequencesCol.setTag(bioTAGS.separator, C.PEPSEA.SEPARATOR);
|
|
59
|
+
alignedSequencesCol.setTag(bioTAGS.aligned, ALIGNMENT.SEQ_MSA);
|
|
60
|
+
alignedSequencesCol.setTag(bioTAGS.alphabet, ALPHABET.UN);
|
|
61
|
+
alignedSequencesCol.semType = DG.SEMTYPE.MACROMOLECULE;
|
|
62
|
+
|
|
63
|
+
return alignedSequencesCol;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function requestAlignedObjects(dockerfileId: string, body: PepseaBodyUnit[], method: string, gapOpen: number,
|
|
67
|
+
gapExtend: number): Promise<PepseaRepsonse> {
|
|
68
|
+
const params = {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
headers: {'Accept': 'application/json', 'Content-Type': 'application/json'},
|
|
71
|
+
body: JSON.stringify(body),
|
|
72
|
+
};
|
|
73
|
+
const path = `/align?method=${method}&gap_open=${gapOpen}&gap_extend=${gapExtend}`;
|
|
74
|
+
//@ts-ignore: this is a temporary workaround for the issue with docker containers
|
|
75
|
+
const response = await (grok.dapi.docker !== undefined ? grok.dapi.docker.dockerContainers : grok.dapi.dockerfiles)
|
|
76
|
+
.request(dockerfileId, path, params);
|
|
77
|
+
return JSON.parse(response ?? '{}');
|
|
78
|
+
}
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import * as DG from 'datagrok-api/dg';
|
|
2
2
|
import * as ui from 'datagrok-api/ui';
|
|
3
3
|
import * as grok from 'datagrok-api/grok';
|
|
4
|
-
import * as bio from '@datagrok-libraries/bio';
|
|
5
4
|
|
|
6
5
|
import wu from 'wu';
|
|
6
|
+
import {splitterAsFasta, SplitterFunc} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
7
|
+
import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
|
|
7
8
|
|
|
8
9
|
const FASTA_LINE_WIDTH = 60;
|
|
9
10
|
|
|
10
11
|
/** Shows dialog to select id columns list and seq column, builds and downloads FASTA content */
|
|
11
12
|
export function saveAsFastaUI() {
|
|
12
13
|
// Use grid for column order adjusted by user
|
|
13
|
-
|
|
14
|
+
const grid: DG.Grid = grok.shell.tv.grid;
|
|
14
15
|
|
|
15
16
|
const idGColList: DG.GridColumn[] = wu.count(0).take(grid.columns.length)
|
|
16
17
|
.map((colI: number) => grid.columns.byIndex(colI)!)
|
|
@@ -27,7 +28,7 @@ export function saveAsFastaUI() {
|
|
|
27
28
|
.filter((gc: DG.GridColumn) => {
|
|
28
29
|
const col: DG.Column | null = gc.column;
|
|
29
30
|
if (col && col.semType === DG.SEMTYPE.MACROMOLECULE) {
|
|
30
|
-
const uh = new
|
|
31
|
+
const uh = new UnitsHandler(col);
|
|
31
32
|
return uh.isFasta();
|
|
32
33
|
}
|
|
33
34
|
return false;
|
|
@@ -39,7 +40,7 @@ export function saveAsFastaUI() {
|
|
|
39
40
|
|
|
40
41
|
const lineWidthInput = ui.intInput('FASTA line width', FASTA_LINE_WIDTH);
|
|
41
42
|
|
|
42
|
-
ui.dialog({title: 'Save as FASTA'
|
|
43
|
+
ui.dialog({title: 'Save as FASTA'})
|
|
43
44
|
.add(ui.inputs([
|
|
44
45
|
idGColListInput,
|
|
45
46
|
seqColInput,
|
|
@@ -57,7 +58,7 @@ export function saveAsFastaUI() {
|
|
|
57
58
|
|
|
58
59
|
const resFastaTxt: string = saveAsFastaDo(valueIdColList, valueSeqCol!, valueLineWidth);
|
|
59
60
|
|
|
60
|
-
const aEl: HTMLAnchorElement = document.createElement('a'
|
|
61
|
+
const aEl: HTMLAnchorElement = document.createElement('a');
|
|
61
62
|
aEl.setAttribute('href', `data:text/plain;charset=utf-8,${encodeURIComponent(resFastaTxt)}`);
|
|
62
63
|
aEl.setAttribute('download', `${grid.dataFrame.name}.fasta`);
|
|
63
64
|
aEl.click();
|
|
@@ -69,7 +70,7 @@ export function saveAsFastaUI() {
|
|
|
69
70
|
export function saveAsFastaDo(
|
|
70
71
|
idColList: DG.Column[], seqCol: DG.Column, lineWidth: number = FASTA_LINE_WIDTH, lineSeparator: string = '\n'
|
|
71
72
|
): string {
|
|
72
|
-
const splitter:
|
|
73
|
+
const splitter: SplitterFunc = splitterAsFasta;
|
|
73
74
|
|
|
74
75
|
const fastaLines: string[] = [];
|
|
75
76
|
|
|
@@ -91,7 +92,7 @@ export function saveAsFastaDo(
|
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
/* split sequence for monomers to prevent wrapping monomer partially */
|
|
94
|
-
export function wrapSequence(seq: string, splitter:
|
|
95
|
+
export function wrapSequence(seq: string, splitter: SplitterFunc, lineWidth: number = FASTA_LINE_WIDTH): string[] {
|
|
95
96
|
const seqMonomerList = splitter(seq);
|
|
96
97
|
let seqPos: number = 0;
|
|
97
98
|
const seqLength: number = seqMonomerList.length;
|
|
@@ -106,4 +107,4 @@ export function wrapSequence(seq: string, splitter: bio.SplitterFunc, lineWidth:
|
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
return seqLineList;
|
|
109
|
-
}
|
|
110
|
+
}
|
package/src/utils/ui-utils.ts
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
|
+
import * as grok from 'datagrok-api/grok';
|
|
2
|
+
import * as DG from 'datagrok-api/dg';
|
|
3
|
+
|
|
4
|
+
export function getMacromoleculeColumn(): DG.Column | any {
|
|
5
|
+
const col = grok.shell.t.columns.bySemType(DG.SEMTYPE.MACROMOLECULE);
|
|
6
|
+
if (col === null) {
|
|
7
|
+
grok.shell.error('Current table does not contain macromolecules');
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
return col;
|
|
11
|
+
}
|
|
12
|
+
|
|
1
13
|
export function updateDivInnerHTML(div: HTMLElement, content: string | Node): void {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
14
|
+
div.innerHTML = '';
|
|
15
|
+
div.append(content);
|
|
16
|
+
}
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import * as ui from 'datagrok-api/ui';
|
|
2
|
-
import * as grok from 'datagrok-api/grok';
|
|
3
2
|
import * as DG from 'datagrok-api/dg';
|
|
4
|
-
import * as bio from '@datagrok-libraries/bio';
|
|
5
3
|
|
|
6
|
-
|
|
4
|
+
import * as rxjs from 'rxjs';
|
|
5
|
+
import {FilterSources, WebLogoViewer, PROPS as wlPROPS} from '../viewers/web-logo-viewer';
|
|
6
|
+
import {
|
|
7
|
+
VdRegionsPropsDefault, VdRegionsProps, IVdRegionsViewer,
|
|
8
|
+
VdRegion, VdRegionType
|
|
9
|
+
} from '@datagrok-libraries/bio/src/viewers/vd-regions';
|
|
10
|
+
import {PositionHeight} from '@datagrok-libraries/bio/src/viewers/web-logo';
|
|
11
|
+
import {Unsubscribable} from 'rxjs';
|
|
12
|
+
|
|
13
|
+
const vrt = VdRegionType;
|
|
7
14
|
|
|
8
15
|
// Positions of regions for numbering schemes
|
|
9
16
|
// http://www.bioinf.org.uk/abs/info.html
|
|
@@ -34,7 +41,9 @@ const vrt = bio.VdRegionType;
|
|
|
34
41
|
/** Viewer with tabs based on description of chain regions.
|
|
35
42
|
* Used to define regions of an immunoglobulin LC.
|
|
36
43
|
*/
|
|
37
|
-
export class VdRegionsViewer extends DG.JsViewer implements
|
|
44
|
+
export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
45
|
+
private viewed: boolean = false;
|
|
46
|
+
|
|
38
47
|
// private regionsDf: DG.DataFrame;
|
|
39
48
|
private regionsFg: DG.FilterGroup | null = null;
|
|
40
49
|
// private regionsTV: DG.TableView;
|
|
@@ -43,43 +52,29 @@ export class VdRegionsViewer extends DG.JsViewer implements bio.IVdRegionsViewer
|
|
|
43
52
|
private isOpened: boolean = false;
|
|
44
53
|
private panelNode: DG.DockNode | null = null;
|
|
45
54
|
|
|
46
|
-
public regions:
|
|
47
|
-
public regionTypes:
|
|
55
|
+
public regions: VdRegion[] = [];
|
|
56
|
+
public regionTypes: VdRegionType[];
|
|
48
57
|
public chains: string[];
|
|
49
|
-
public sequenceColumnNamePostfix: string;
|
|
58
|
+
// public sequenceColumnNamePostfix: string;
|
|
50
59
|
|
|
51
60
|
public skipEmptyPositions: boolean;
|
|
52
61
|
public positionWidth: number;
|
|
53
|
-
public positionHeight:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
public get df(): DG.DataFrame {
|
|
57
|
-
return this.dataFrame;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// TODO: .onTableAttached is not calling on dataFrame set, onPropertyChanged also not calling
|
|
61
|
-
public async setDf(value: DG.DataFrame, regions: bio.VdRegion[]) {
|
|
62
|
-
console.debug('VdRegionsViewer.setDf()');
|
|
63
|
-
await this.destroyView();
|
|
64
|
-
this.regions = regions;
|
|
65
|
-
this.dataFrame = value;
|
|
66
|
-
await this.buildView();
|
|
67
|
-
}
|
|
62
|
+
public positionHeight: PositionHeight;
|
|
68
63
|
|
|
69
64
|
constructor() {
|
|
70
65
|
super();
|
|
71
66
|
|
|
72
67
|
// To prevent ambiguous numbering scheme in MLB
|
|
73
68
|
this.regionTypes = this.stringList('regionTypes', [vrt.CDR],
|
|
74
|
-
{choices: Object.values(vrt).filter((t) => t != vrt.Unknown)});
|
|
69
|
+
{choices: Object.values(vrt).filter((t) => t != vrt.Unknown)}) as VdRegionType[];
|
|
75
70
|
this.chains = this.stringList('chains', ['Heavy', 'Light'],
|
|
76
71
|
{choices: ['Heavy', 'Light']});
|
|
77
|
-
this.sequenceColumnNamePostfix = this.string('sequenceColumnNamePostfix', 'chain sequence');
|
|
72
|
+
// this.sequenceColumnNamePostfix = this.string('sequenceColumnNamePostfix', 'chain sequence');
|
|
78
73
|
|
|
79
74
|
this.skipEmptyPositions = this.bool('skipEmptyPositions', false);
|
|
80
75
|
this.positionWidth = this.float('positionWidth', 16);
|
|
81
|
-
this.positionHeight = this.string('positionHeight',
|
|
82
|
-
{choices: Object.keys(
|
|
76
|
+
this.positionHeight = this.string('positionHeight', PositionHeight.Entropy,
|
|
77
|
+
{choices: Object.keys(PositionHeight)}) as PositionHeight;
|
|
83
78
|
}
|
|
84
79
|
|
|
85
80
|
public async init() {
|
|
@@ -107,18 +102,30 @@ export class VdRegionsViewer extends DG.JsViewer implements bio.IVdRegionsViewer
|
|
|
107
102
|
// this.mlbView.dockManager.dock(this.regionsFg.root, DG.DOCK_TYPE.LEFT, rootNode, 'Filter regions', 0.2);
|
|
108
103
|
|
|
109
104
|
this.subs.push(ui.onSizeChanged(this.root).subscribe(this.rootOnSizeChanged.bind(this)));
|
|
110
|
-
|
|
111
|
-
this.root.addEventListener('mousemove', this.onMouseMoveRoot.bind(this));
|
|
105
|
+
this.subs.push(rxjs.fromEvent<MouseEvent>(this.root, 'mousemove').subscribe(this.rootOnMouseMove.bind(this)));
|
|
112
106
|
|
|
113
|
-
await this.buildView();
|
|
107
|
+
// await this.buildView('init'); // init
|
|
114
108
|
}
|
|
115
109
|
|
|
116
110
|
public override async onTableAttached() {
|
|
117
|
-
|
|
111
|
+
const superOnTableAttached = super.onTableAttached.bind(this);
|
|
112
|
+
this.viewPromise = this.viewPromise.then(async () => { // onTableAttached
|
|
113
|
+
superOnTableAttached();
|
|
114
|
+
if (!this.viewed) {
|
|
115
|
+
await this.buildView('onTableAttached'); // onTableAttached
|
|
116
|
+
this.viewed = true;
|
|
117
|
+
}
|
|
118
|
+
});
|
|
118
119
|
}
|
|
119
120
|
|
|
120
|
-
public override
|
|
121
|
+
public override onPropertyChanged(property: DG.Property | null): void {
|
|
121
122
|
super.onPropertyChanged(property);
|
|
123
|
+
|
|
124
|
+
if (!property) {
|
|
125
|
+
console.warn('Bio: VdRegionsViewer.onPropertyChanged() property is null');
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
122
129
|
if (property) {
|
|
123
130
|
switch (property.name) {
|
|
124
131
|
case 'regionTypes':
|
|
@@ -127,7 +134,6 @@ export class VdRegionsViewer extends DG.JsViewer implements bio.IVdRegionsViewer
|
|
|
127
134
|
break;
|
|
128
135
|
case 'sequenceColumnNamePostfix':
|
|
129
136
|
break;
|
|
130
|
-
case 'skipEmptyPositions':
|
|
131
137
|
// for (let orderI = 0; orderI < this.logos.length; orderI++) {
|
|
132
138
|
// for (let chainI = 0; chainI < this.chains.length; chainI++) {
|
|
133
139
|
// const chain: string = this.chains[chainI];
|
|
@@ -135,56 +141,72 @@ export class VdRegionsViewer extends DG.JsViewer implements bio.IVdRegionsViewer
|
|
|
135
141
|
// }
|
|
136
142
|
// }
|
|
137
143
|
// this.calcSize();
|
|
138
|
-
await this.destroyView();
|
|
139
|
-
await this.buildView();
|
|
140
|
-
break;
|
|
141
|
-
case 'positionWidth':
|
|
142
|
-
await this.destroyView();
|
|
143
|
-
await this.buildView();
|
|
144
|
-
break;
|
|
145
|
-
|
|
146
|
-
case 'positionHeight':
|
|
147
|
-
await this.destroyView();
|
|
148
|
-
await this.buildView();
|
|
149
|
-
break;
|
|
150
144
|
}
|
|
151
145
|
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
public async reset() {
|
|
156
146
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
this.
|
|
162
|
-
|
|
147
|
+
switch (property.name) {
|
|
148
|
+
case 'skipEmptyPositions':
|
|
149
|
+
case 'positionWidth':
|
|
150
|
+
case 'positionHeight':
|
|
151
|
+
this.setData(this.dataFrame, this.regions); // onPropertyChanged
|
|
152
|
+
break;
|
|
163
153
|
}
|
|
164
154
|
}
|
|
165
155
|
|
|
166
|
-
|
|
156
|
+
// -- Data --
|
|
167
157
|
|
|
168
|
-
|
|
158
|
+
// TODO: .onTableAttached is not calling on dataFrame set, onPropertyChanged also not calling
|
|
159
|
+
public setData(mlbDf: DG.DataFrame, regions: VdRegion[]) {
|
|
160
|
+
console.debug('Bio: VdRegionsViewer.setData()');
|
|
161
|
+
this.viewPromise = this.viewPromise.then(async () => { // setData
|
|
162
|
+
if (this.viewed) {
|
|
163
|
+
await this.destroyView('setData'); // setData
|
|
164
|
+
this.viewed = false;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
169
167
|
|
|
170
|
-
|
|
168
|
+
this.regions = regions;
|
|
169
|
+
this.dataFrame = mlbDf; // causes detach and onTableAttached
|
|
171
170
|
|
|
172
|
-
|
|
171
|
+
this.viewPromise = this.viewPromise.then(async () => { // setData
|
|
172
|
+
if (!this.viewed) {
|
|
173
|
+
await this.buildView('setData'); // setData
|
|
174
|
+
this.viewed = true;
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
173
178
|
|
|
174
|
-
|
|
175
|
-
|
|
179
|
+
override detach() {
|
|
180
|
+
const superDetach = super.detach.bind(this);
|
|
181
|
+
this.viewPromise = this.viewPromise.then(async () => { // detach
|
|
182
|
+
if (this.viewed) {
|
|
183
|
+
await this.destroyView('detach'); // detach
|
|
184
|
+
this.viewed = false;
|
|
185
|
+
}
|
|
186
|
+
superDetach();
|
|
187
|
+
});
|
|
176
188
|
}
|
|
177
189
|
|
|
178
|
-
//
|
|
190
|
+
// -- View --
|
|
191
|
+
|
|
192
|
+
private viewPromise: Promise<void> = Promise.resolve();
|
|
179
193
|
|
|
180
|
-
//#region -- View --
|
|
181
194
|
private host: HTMLElement | null = null;
|
|
195
|
+
private filterSourceInput: DG.InputBase<boolean | null> | null = null;
|
|
182
196
|
private mainLayout: HTMLTableElement | null = null;
|
|
183
|
-
private logos: { [chain: string]:
|
|
197
|
+
private logos: { [chain: string]: WebLogoViewer }[] = [];
|
|
198
|
+
|
|
199
|
+
private viewSubs: Unsubscribable[] = [];
|
|
184
200
|
|
|
185
|
-
private async destroyView(): Promise<void> {
|
|
201
|
+
private async destroyView(purpose: string): Promise<void> {
|
|
186
202
|
// TODO: Unsubscribe from and remove all view elements
|
|
187
|
-
console.debug(`VdRegionsViewer.destroyView( mainLayout = ${!this.mainLayout ? 'none' : 'value'} )`
|
|
203
|
+
console.debug(`Bio: VdRegionsViewer.destroyView( mainLayout = ${!this.mainLayout ? 'none' : 'value'} ), ` +
|
|
204
|
+
`purpose = '${purpose}'`);
|
|
205
|
+
if (this.filterSourceInput) {
|
|
206
|
+
//
|
|
207
|
+
ui.empty(this.filterSourceInput.root);
|
|
208
|
+
}
|
|
209
|
+
|
|
188
210
|
if (this.mainLayout != null) {
|
|
189
211
|
// this.root.removeChild(this.host);
|
|
190
212
|
this.mainLayout.remove();
|
|
@@ -192,34 +214,31 @@ export class VdRegionsViewer extends DG.JsViewer implements bio.IVdRegionsViewer
|
|
|
192
214
|
this.host = null;
|
|
193
215
|
this.mainLayout = null;
|
|
194
216
|
}
|
|
195
|
-
}
|
|
196
217
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const colNames: { [chain: string]: string } = Object.assign({},
|
|
201
|
-
...this.chains.map((chain) => ({[chain]: `${chain} ${this.sequenceColumnNamePostfix}`})));
|
|
218
|
+
for (const sub of this.viewSubs) sub.unsubscribe();
|
|
219
|
+
}
|
|
202
220
|
|
|
203
|
-
|
|
221
|
+
private async buildView(purpose: string): Promise<void> {
|
|
222
|
+
console.debug(`Bio: VdRegionsViewer.buildView() begin, ` + `purpose = '${purpose}'`);
|
|
204
223
|
|
|
224
|
+
const regionsFiltered: VdRegion[] = this.regions.filter((r: VdRegion) => this.regionTypes.includes(r.type));
|
|
205
225
|
const orderList: number[] = Array.from(new Set(regionsFiltered.map((r) => r.order))).sort();
|
|
206
226
|
|
|
207
227
|
this.logos = [];
|
|
208
|
-
|
|
209
228
|
for (let orderI = 0; orderI < orderList.length; orderI++) {
|
|
210
|
-
const regionChains: { [chain: string]:
|
|
229
|
+
const regionChains: { [chain: string]: WebLogoViewer } = {};
|
|
211
230
|
for (const chain of this.chains) {
|
|
212
|
-
const region:
|
|
231
|
+
const region: VdRegion | undefined = regionsFiltered
|
|
213
232
|
.find((r) => r.order == orderList[orderI] && r.chain == chain);
|
|
214
233
|
regionChains[chain] = (await this.dataFrame.plot.fromType('WebLogo', {
|
|
215
|
-
sequenceColumnName:
|
|
234
|
+
sequenceColumnName: region!.sequenceColumnName,
|
|
216
235
|
startPositionName: region!.positionStartName,
|
|
217
236
|
endPositionName: region!.positionEndName,
|
|
218
237
|
fixWidth: true,
|
|
219
238
|
skipEmptyPositions: this.skipEmptyPositions,
|
|
220
239
|
positionWidth: this.positionWidth,
|
|
221
240
|
positionHeight: this.positionHeight,
|
|
222
|
-
})) as unknown as
|
|
241
|
+
})) as unknown as WebLogoViewer;
|
|
223
242
|
}
|
|
224
243
|
// WebLogo creation fires onRootSizeChanged event even before control being added to this.logos
|
|
225
244
|
this.logos[orderI] = regionChains;
|
|
@@ -244,7 +263,7 @@ export class VdRegionsViewer extends DG.JsViewer implements bio.IVdRegionsViewer
|
|
|
244
263
|
})] : []),
|
|
245
264
|
// List with controls for regions
|
|
246
265
|
...[...Array(orderList.length).keys()].map((orderI) => {
|
|
247
|
-
const wl:
|
|
266
|
+
const wl: WebLogoViewer = this.logos[orderI][chain];
|
|
248
267
|
wl.root.style.height = '100%';
|
|
249
268
|
|
|
250
269
|
const resDiv = ui.div([wl.root]/*`${chain} ${regionsFiltered[rI]}`*/, {
|
|
@@ -261,7 +280,7 @@ export class VdRegionsViewer extends DG.JsViewer implements bio.IVdRegionsViewer
|
|
|
261
280
|
},
|
|
262
281
|
['', ...[...Array(orderList.length).keys()].map(
|
|
263
282
|
(orderI: number) => regionsFiltered.find(
|
|
264
|
-
(r:
|
|
283
|
+
(r: VdRegion) => r.order == orderList[orderI] && r.chain == this.chains[0]
|
|
265
284
|
)!.name || 'Name')]
|
|
266
285
|
);
|
|
267
286
|
this.mainLayout.className = 'mlb-vd-regions-viewer-table2';
|
|
@@ -269,15 +288,21 @@ export class VdRegionsViewer extends DG.JsViewer implements bio.IVdRegionsViewer
|
|
|
269
288
|
// this.mainLayout.style.height = '100%';
|
|
270
289
|
// this.mainLayout.style.border = '1px solid black';
|
|
271
290
|
|
|
291
|
+
this.filterSourceInput = ui.boolInput('', false, this.filterSourceInputOnValueChanged.bind(this));
|
|
292
|
+
this.filterSourceInput.root.style.position = 'absolute';
|
|
293
|
+
this.filterSourceInput.root.style.left = '10px';
|
|
294
|
+
this.filterSourceInput.root.style.top = '-3px';
|
|
295
|
+
ui.tooltip.bind(this.filterSourceInput.root, 'Check to filter sequences for selected VRs');
|
|
296
|
+
|
|
272
297
|
const color: string = `#ffbb${Math.ceil(Math.random() * 255).toString(16)}`;
|
|
273
|
-
this.host = ui.
|
|
298
|
+
this.host = ui.div([this.mainLayout, this.filterSourceInput!.root],
|
|
274
299
|
{/*style: {backgroundColor: color}*/});
|
|
275
300
|
this.root.appendChild(this.host);
|
|
276
301
|
this.root.style.overflowX = 'auto';
|
|
277
302
|
|
|
278
303
|
this.calcSize();
|
|
279
304
|
|
|
280
|
-
console.debug('VdRegionsViewer.buildView() end');
|
|
305
|
+
console.debug('Bio: VdRegionsViewer.buildView() end');
|
|
281
306
|
}
|
|
282
307
|
|
|
283
308
|
private calcSize() {
|
|
@@ -296,10 +321,27 @@ export class VdRegionsViewer extends DG.JsViewer implements bio.IVdRegionsViewer
|
|
|
296
321
|
}
|
|
297
322
|
}
|
|
298
323
|
|
|
299
|
-
|
|
324
|
+
// -- Handle events --
|
|
300
325
|
|
|
301
|
-
private
|
|
326
|
+
private rootOnSizeChanged(args: any): void {
|
|
327
|
+
this.calcSize();
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
private rootOnMouseMove(e: MouseEvent) {
|
|
302
331
|
// ui.tooltip.show('text', e.x + 8, e.y + 8,);
|
|
303
332
|
// console.log(`onMouseMoveRoot.( x: ${e.x}, y: ${e.y} )`);
|
|
304
333
|
}
|
|
334
|
+
|
|
335
|
+
private filterSourceInputOnValueChanged(): void {
|
|
336
|
+
const filterSource: FilterSources = this.filterSourceInput!.value == true ?
|
|
337
|
+
FilterSources.Selected : FilterSources.Filtered;
|
|
338
|
+
|
|
339
|
+
for (let orderI = 0; orderI < this.logos.length; orderI++) {
|
|
340
|
+
for (let chainI = 0; chainI < this.chains.length; chainI++) {
|
|
341
|
+
const chain: string = this.chains[chainI];
|
|
342
|
+
const wl: DG.JsViewer = this.logos[orderI][chain];
|
|
343
|
+
wl.setOptions({[wlPROPS.filterSource]: filterSource});
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
305
347
|
}
|