@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.
Files changed (64) hide show
  1. package/README.md +11 -12
  2. package/css/helm.css +10 -0
  3. package/detectors.js +83 -59
  4. package/dist/package-test.js +2 -68651
  5. package/dist/package-test.js.map +1 -0
  6. package/dist/package.js +2 -66040
  7. package/dist/package.js.map +1 -0
  8. package/dockerfiles/Dockerfile +86 -0
  9. package/files/icons/composition-analysis.svg +17 -0
  10. package/files/icons/sequence-diversity-viewer.svg +4 -0
  11. package/files/icons/sequence-similarity-viewer.svg +4 -0
  12. package/files/icons/vdregions-viewer.svg +22 -0
  13. package/files/icons/weblogo-viewer.svg +7 -0
  14. package/files/tests/testUrl.csv +11 -0
  15. package/files/tests/toAtomicLevelTest.csv +4 -0
  16. package/package.json +29 -32
  17. package/src/analysis/sequence-activity-cliffs.ts +15 -13
  18. package/src/analysis/sequence-diversity-viewer.ts +3 -2
  19. package/src/analysis/sequence-search-base-viewer.ts +4 -2
  20. package/src/analysis/sequence-similarity-viewer.ts +4 -4
  21. package/src/analysis/sequence-space.ts +2 -1
  22. package/src/calculations/monomerLevelMols.ts +6 -6
  23. package/src/package-test.ts +9 -2
  24. package/src/package.ts +230 -145
  25. package/src/substructure-search/substructure-search.ts +25 -22
  26. package/src/tests/Palettes-test.ts +9 -9
  27. package/src/tests/WebLogo-positions-test.ts +131 -68
  28. package/src/tests/_first-tests.ts +9 -0
  29. package/src/tests/activity-cliffs-tests.ts +8 -7
  30. package/src/tests/activity-cliffs-utils.ts +17 -9
  31. package/src/tests/bio-tests.ts +30 -21
  32. package/src/tests/checkInputColumn-tests.ts +17 -17
  33. package/src/tests/converters-test.ts +81 -46
  34. package/src/tests/detectors-benchmark-tests.ts +17 -17
  35. package/src/tests/detectors-tests.ts +190 -178
  36. package/src/tests/fasta-export-tests.ts +2 -3
  37. package/src/tests/monomer-libraries-tests.ts +34 -0
  38. package/src/tests/pepsea-tests.ts +21 -0
  39. package/src/tests/renderers-test.ts +33 -29
  40. package/src/tests/sequence-space-test.ts +6 -4
  41. package/src/tests/similarity-diversity-tests.ts +4 -4
  42. package/src/tests/splitters-test.ts +6 -7
  43. package/src/tests/substructure-filters-tests.ts +23 -1
  44. package/src/tests/utils/sequences-generators.ts +7 -7
  45. package/src/tests/utils.ts +2 -1
  46. package/src/tests/viewers.ts +16 -0
  47. package/src/utils/cell-renderer.ts +116 -54
  48. package/src/utils/constants.ts +7 -6
  49. package/src/utils/convert.ts +17 -11
  50. package/src/utils/monomer-lib.ts +174 -0
  51. package/src/utils/multiple-sequence-alignment.ts +49 -26
  52. package/src/utils/pepsea.ts +78 -0
  53. package/src/utils/save-as-fasta.ts +9 -8
  54. package/src/utils/ui-utils.ts +15 -3
  55. package/src/viewers/vd-regions-viewer.ts +125 -83
  56. package/src/viewers/web-logo-viewer.ts +1031 -0
  57. package/src/widgets/bio-substructure-filter.ts +38 -24
  58. package/tsconfig.json +71 -72
  59. package/webpack.config.js +4 -11
  60. package/dist/vendors-node_modules_datagrok-libraries_ml_src_workers_dimensionality-reducer_js.js +0 -8988
  61. package/jest.config.js +0 -33
  62. package/src/__jest__/remote.test.ts +0 -77
  63. package/src/__jest__/test-node.ts +0 -98
  64. 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
- import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
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, isAligned = false, unUsedName: string = ''): Promise<DG.Column> {
34
- let sequences = srcCol.toList();
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, _) => AlignedSequenceEncoder.clean(v).replace(/\-/g, ''));
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
- console.log(['fasta.length =', fasta.length]);
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
- await CLI.fs.writeFile('input.fa', fasta);
48
- const output = await CLI.exec('kalign input.fa -f fasta -o result.fasta');
49
- console.warn(output);
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
- const buf = await CLI.cat('result.fasta');
52
- if (!buf)
53
- throw new Error(`kalign output no result`);
71
+ const buf = await CLI.cat(fastaOutputFilename);
72
+ if (!buf)
73
+ throw new Error(`kalign output no result`);
54
74
 
55
- const ffh = new FastaFileHandler(buf);
56
- const aligned = ffh.sequencesArray; // array of sequences extracted from FASTA
57
- const tgtCol = DG.Column.fromStrings(unUsedName, aligned);
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(bio.TAGS.aligned);
85
+ const srcAligned = srcCol.getTag(bioTAGS.aligned);
63
86
  const tgtAligned = srcAligned + '.MSA';
64
87
  //alphabet
65
- const srcAlphabet = srcCol.getTag(bio.TAGS.alphabet);
88
+ const srcAlphabet = srcCol.getTag(bioTAGS.alphabet);
66
89
 
67
90
  tgtCol.setTag(DG.TAGS.UNITS, srcUnits);
68
- tgtCol.setTag(bio.TAGS.aligned, tgtAligned);
69
- tgtCol.setTag(bio.TAGS.alphabet, srcAlphabet);
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
- let grid: DG.Grid = grok.shell.tv.grid;
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 bio.UnitsHandler(col);
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: bio.SplitterFunc = bio.splitterAsFasta;
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: bio.SplitterFunc, lineWidth: number = FASTA_LINE_WIDTH): string[] {
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
+ }
@@ -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
- div.innerHTML = '';
3
- div.append(content);
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
- const vrt = bio.VdRegionType;
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 bio.IVdRegionsViewer {
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: bio.VdRegion[] = [];
47
- public regionTypes: string[];
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: string;
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', bio.PositionHeight.Entropy,
82
- {choices: Object.keys(bio.PositionHeight)});
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
- // rxjs.fromEvent(this.root, 'mousemove').subscribe(this.onMouseMoveRoot.bind(this));
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
- await this.init();
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 async onPropertyChanged(property: DG.Property | null) {
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
- public async open(mlbView: DG.TableView) {
160
- if (!this.isOpened) {
161
- this.isOpened = true;
162
- this.panelNode = mlbView.dockManager.dock(this.root, DG.DOCK_TYPE.TOP, null, 'Regions', 0.2);
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
- public async show(mlbView: DG.TableView) {
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
- // #region -- Handle controls' events --
168
+ this.regions = regions;
169
+ this.dataFrame = mlbDf; // causes detach and onTableAttached
171
170
 
172
- private resizing: boolean = false;
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
- private rootOnSizeChanged(args: any): void {
175
- this.calcSize();
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
- // #endregion
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]: bio.WebLogoViewer }[] = [];
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
- private async buildView(): Promise<void> {
198
- console.debug('VdRegionsViewer.buildView() start');
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
- const regionsFiltered: bio.VdRegion[] = this.regions.filter((r: bio.VdRegion) => this.regionTypes.includes(r.type));
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]: bio.WebLogoViewer } = {};
229
+ const regionChains: { [chain: string]: WebLogoViewer } = {};
211
230
  for (const chain of this.chains) {
212
- const region: bio.VdRegion | undefined = regionsFiltered
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: colNames[chain],
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 bio.WebLogoViewer;
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: bio.WebLogoViewer = this.logos[orderI][chain];
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: bio.VdRegion) => r.order == orderList[orderI] && r.chain == this.chains[0]
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.box(this.mainLayout,
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
- //#endregion -- View --
324
+ // -- Handle events --
300
325
 
301
- private onMouseMoveRoot(e: MouseEvent) {
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
  }