@datagrok/peptides 0.8.13 → 1.0.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.
Files changed (39) hide show
  1. package/.eslintrc.json +5 -2
  2. package/dist/package-test.js +1268 -1766
  3. package/dist/package.js +1097 -1622
  4. package/dist/vendors-node_modules_datagrok-libraries_ml_src_workers_dimensionality-reducer_js.js +120 -62
  5. package/package.json +13 -17
  6. package/package.png +0 -0
  7. package/src/model.ts +504 -448
  8. package/src/monomer-library.ts +31 -30
  9. package/src/package-test.ts +5 -6
  10. package/src/package.ts +52 -70
  11. package/src/tests/core.ts +67 -0
  12. package/src/tests/msa-tests.ts +3 -3
  13. package/src/tests/peptide-space-test.ts +65 -45
  14. package/src/tests/utils.ts +20 -50
  15. package/src/utils/cell-renderer.ts +25 -151
  16. package/src/utils/chem-palette.ts +3 -14
  17. package/src/utils/constants.ts +5 -0
  18. package/src/utils/filtering-statistics.ts +2 -2
  19. package/src/utils/misc.ts +29 -0
  20. package/src/utils/multiple-sequence-alignment.ts +5 -18
  21. package/src/utils/multivariate-analysis.ts +5 -8
  22. package/src/utils/peptide-similarity-space.ts +12 -9
  23. package/src/utils/types.ts +5 -2
  24. package/src/viewers/peptide-space-viewer.ts +67 -39
  25. package/src/viewers/sar-viewer.ts +34 -37
  26. package/src/viewers/stacked-barchart-viewer.ts +38 -61
  27. package/src/widgets/analyze-peptides.ts +53 -75
  28. package/src/widgets/distribution.ts +34 -18
  29. package/src/widgets/manual-alignment.ts +8 -12
  30. package/src/widgets/peptide-molecule.ts +48 -25
  31. package/src/widgets/subst-table.ts +53 -52
  32. package/src/workers/dimensionality-reducer.ts +8 -13
  33. package/{test-Peptides-f8114def7953-4bf59d70.html → test-Peptides-69a4761f6044-40ac3a0c.html} +2 -2
  34. package/src/peptides.ts +0 -327
  35. package/src/semantics.ts +0 -5
  36. package/src/tests/peptides-tests.ts +0 -60
  37. package/src/utils/SAR-multiple-filter.ts +0 -439
  38. package/src/utils/SAR-multiple-selection.ts +0 -177
  39. package/src/viewers/logo-viewer.ts +0 -195
@@ -1,10 +1,13 @@
1
1
  import * as DG from 'datagrok-api/dg';
2
2
 
3
- export type SubstitutionCases = {[aar: string]: number[][][]};
4
- export type SubstitutionTooltips = { [aar: string]: {}[][]; };
5
3
  export type DataFrameDict = {[key: string]: DG.DataFrame};
6
4
 
7
5
  export namespace BarChart {
8
6
  export type BarPart = {colName : string, aaName : string};
9
7
  export type BarStatsObject = {name: string, count: number, selectedCount: number};
10
8
  }
9
+
10
+ export type UTypedArray = Uint8Array | Uint16Array | Uint32Array;
11
+ //AAR: (Position: (index: indexList))
12
+ export type SubstitutionsInfo = Map<string, Map<string, Map<number, number[] | UTypedArray>>>;
13
+ export type SelectionObject = {[postiton: string]: string[]};
@@ -6,21 +6,21 @@ import $ from 'cash-dom';
6
6
 
7
7
  import {getSequenceMolecularWeight} from '../utils/molecular-measure';
8
8
  import {AlignedSequenceEncoder} from '@datagrok-libraries/bio/src/sequence-encoder';
9
- import {
10
- createDimensinalityReducingWorker,
9
+ import {createDimensinalityReducingWorker, IReduceDimensionalityResult,
11
10
  } from '@datagrok-libraries/ml/src/workers/dimensionality-reducing-worker-creator';
12
11
  import {StringMetrics} from '@datagrok-libraries/ml/src/typed-metrics';
13
- import {Coordinates} from '@datagrok-libraries/utils/src/type-declarations';
14
12
  import * as C from '../utils/constants';
15
- import {PeptidesController} from '../peptides';
13
+ import {PeptidesModel} from '../model';
16
14
 
17
15
  export class PeptideSpaceViewer extends DG.JsViewer {
18
16
  method: string;
19
17
  measure: string;
20
18
  cyclesCount: number;
21
- // controller: PeptidesController | null = null;
22
19
  customProperties = new Set(['method', 'measure', 'cyclesCount']);
23
20
  isEmbeddingCreating: boolean = false;
21
+ model!: PeptidesModel;
22
+ //FIXME: even if the property stays the same, for some reason it's still triggering prop change
23
+ prevProps!: {'method': string, 'measure': string, 'cyclesCount': number};
24
24
 
25
25
  constructor() {
26
26
  super();
@@ -30,35 +30,60 @@ export class PeptideSpaceViewer extends DG.JsViewer {
30
30
  const measureChoices = ['Levenshtein', 'Jaro-Winkler'];
31
31
  this.measure = this.addProperty('measure', DG.TYPE.STRING, 'Levenshtein', {choices: measureChoices});
32
32
  this.cyclesCount = this.addProperty('cyclesCount', DG.TYPE.INT, 100);
33
+ this.updatePrevProperties();
33
34
  }
34
35
 
35
- async onFrameAttached(dataFrame: DG.DataFrame) {
36
- super.onFrameAttached(dataFrame);
36
+ updatePrevProperties(): void {
37
+ this.prevProps = {'method': this.method, 'measure': this.measure, 'cyclesCount': this.cyclesCount};
38
+ }
39
+
40
+ async onTableAttached(): Promise<void> {
41
+ super.onTableAttached();
37
42
 
38
- // this.controller = await PeptidesController.getInstance(this.dataFrame!);
43
+ this.model = await PeptidesModel.getInstance(this.dataFrame);
39
44
 
40
- await this.render(this.dataFrame!.temp[C.EMBEDDING_STATUS]);
45
+ await this.render(!this.dataFrame.temp[C.EMBEDDING_STATUS]);
41
46
  }
42
47
 
43
- async onPropertyChanged(property: DG.Property | null) {
48
+ async onPropertyChanged(property: DG.Property | null): Promise<void> {
44
49
  super.onPropertyChanged(property);
50
+ if (this.prevProps[property?.name as 'method' | 'measure' | 'cyclesCount' ?? ''] == property?.get(this))
51
+ return;
45
52
 
53
+ if (this.model)
54
+ await this.render(this.customProperties.has(property?.name ?? '') || !this.dataFrame.temp[C.EMBEDDING_STATUS]);
46
55
 
47
- await this.render(this.customProperties.has(property?.name ?? '') || this.dataFrame!.temp[C.EMBEDDING_STATUS]);
56
+ this.updatePrevProperties();
48
57
  }
49
58
 
50
- async render(computeData=false) {
51
- if (computeData && !this.isEmbeddingCreating) {
59
+ async render(computeData=false): Promise<void> {
60
+ if (computeData && !this.isEmbeddingCreating && !this.model.isChangingEdfBitset) {
52
61
  this.isEmbeddingCreating = true;
53
62
  $(this.root).empty();
54
63
  const viewerHost = ui.waitBox(async () => {
55
- await computeWeights(this.dataFrame!, this.method, this.measure, this.cyclesCount);
64
+ const edf = await computeWeights(this.dataFrame, this.method, this.measure, this.cyclesCount);
65
+ this.dataFrame.temp[C.EMBEDDING_STATUS] = true;
66
+ this.model.edf = edf;
67
+
68
+ if (edf === null)
69
+ return ui.label('Could not compute embeddings');
70
+
71
+ const edfSelection = edf.selection;
72
+ edfSelection.copyFrom(this.model.getBiteset());
73
+ edfSelection.onChanged.subscribe(() => {
74
+ if (!this.model.isChangingEdfBitset)
75
+ this.model.fireBitsetChanged(true);
76
+ });
77
+
78
+ const colorCol = this.dataFrame.columns.bySemType(C.SEM_TYPES.ACTIVITY_SCALED)!;
79
+ edf.columns.add(colorCol);
56
80
 
57
81
  const viewerOptions = {
58
- x: '~X', y: '~Y', color: C.COLUMNS_NAMES.ACTIVITY_SCALED ?? '~MW', size: '~MW', title: 'Peptide Space',
82
+ x: '~X', y: '~Y', color: colorCol.name ?? '~MW', size: '~MW', title: 'Peptide Space',
59
83
  showYSelector: false, showXSelector: false, showColorSelector: false, showSizeSelector: false,
84
+ zoomAndFilter: 'no action', axesFollowFilter: false,
60
85
  };
61
- const viewerRoot = this.dataFrame!.plot.scatter(viewerOptions).root;
86
+ const viewerRoot = edf.plot.scatter(viewerOptions).root;
62
87
  viewerRoot.style.width = 'auto';
63
88
  this.isEmbeddingCreating = false;
64
89
  viewerHost.style.paddingLeft = 'unset';
@@ -70,22 +95,24 @@ export class PeptideSpaceViewer extends DG.JsViewer {
70
95
  }
71
96
  }
72
97
 
98
+ //Do not accept table, only column
73
99
  export async function computeWeights(
74
100
  table: DG.DataFrame, method: string, measure: string, cyclesCount: number, col?: DG.Column,
75
- ): Promise<void> {
101
+ ): Promise<DG.DataFrame | null> {
76
102
  const pi = DG.TaskBarProgressIndicator.create('Creating embedding...');
103
+ let edf: DG.DataFrame | null = null;
77
104
  try {
78
105
  const axesNames = ['~X', '~Y', '~MW'];
79
- const columnData = (col ?? table.getCol(C.COLUMNS_NAMES.ALIGNED_SEQUENCE)).toList()
80
- .map((v) => AlignedSequenceEncoder.clean(v));
106
+ col ??= table.columns.bySemType(C.SEM_TYPES.ALIGNED_SEQUENCE)!;
107
+ const columnData = col.toList().map((v) => AlignedSequenceEncoder.clean(v));
81
108
 
82
- const embcols = await createDimensinalityReducingWorker(
83
- {data: columnData, metric: measure as StringMetrics}, method, cyclesCount);
109
+ const reduceDimRes: IReduceDimensionalityResult = await createDimensinalityReducingWorker(
110
+ {data: columnData, metric: measure as StringMetrics}, method, {cycles: cyclesCount});
111
+ const embcols = reduceDimRes.embedding;
84
112
 
85
- const columns = Array.from(
86
- embcols as Coordinates, (v: Float32Array, k) => DG.Column.fromFloat32Array(axesNames[k], v));
113
+ const columns = Array.from(embcols, (v: Float32Array, k) => DG.Column.fromFloat32Array(axesNames[k], v));
87
114
 
88
- function _getMW(sequences: string[]) {
115
+ function _getMW(sequences: string[]): Float32Array {
89
116
  const mw: Float32Array = new Float32Array(sequences.length);
90
117
 
91
118
  mw.map((_, index) => getSequenceMolecularWeight(sequences[index] ?? ''));
@@ -95,27 +122,28 @@ export async function computeWeights(
95
122
 
96
123
  columns.push(DG.Column.fromFloat32Array('~MW', _getMW(columnData)));
97
124
 
98
- const edf = DG.DataFrame.fromColumns(columns);
125
+ edf = DG.DataFrame.fromColumns(columns);
99
126
 
100
127
  // Add new axes.
101
- for (const axis of axesNames) {
102
- const col = table.col(axis);
103
- const newCol = edf.getCol(axis);
104
-
105
- // if (col != null) {
106
- // for (let i = 0; i < newCol.length; ++i) {
107
- // const v = newCol.get(i);
108
- // table.set(axis, i, v);
109
- // }
110
- // } else
111
- // table.columns.insert(newCol);
112
- const columnList = table.columns as DG.ColumnList;
113
- col !== null ? columnList.replace(col, newCol) : columnList.insert(newCol);
114
- }
128
+ // for (const axis of axesNames) {
129
+ // const col = table.col(axis);
130
+ // const newCol = edf.getCol(axis);
131
+
132
+ // // if (col != null) {
133
+ // // for (let i = 0; i < newCol.length; ++i) {
134
+ // // const v = newCol.get(i);
135
+ // // table.set(axis, i, v);
136
+ // // }
137
+ // // } else
138
+ // // table.columns.insert(newCol);
139
+ // const columnList = table.columns;
140
+ // col !== null ? columnList.replace(col, newCol) : columnList.insert(newCol);
141
+ // }
115
142
  } catch (error) {
116
143
  grok.shell.error('Could not compute embeddings. See console for details.');
117
144
  console.error(error);
118
145
  } finally {
119
146
  pi.close();
147
+ return edf;
120
148
  }
121
149
  }
@@ -3,8 +3,8 @@ import * as ui from 'datagrok-api/ui';
3
3
  import * as DG from 'datagrok-api/dg';
4
4
 
5
5
  import $ from 'cash-dom';
6
- import {PeptidesController} from '../peptides';
7
6
  import * as C from '../utils/constants';
7
+ import {PeptidesModel} from '../model';
8
8
 
9
9
  let IS_PROPERTY_CHANGING = false;
10
10
 
@@ -12,11 +12,9 @@ export class SARViewerBase extends DG.JsViewer {
12
12
  tempName!: string;
13
13
  viewerGrid!: DG.Grid;
14
14
  sourceGrid!: DG.Grid;
15
- controller!: PeptidesController;
15
+ model!: PeptidesModel;
16
16
  scaling: string;
17
- // filterMode: boolean;
18
17
  bidirectionalAnalysis: boolean;
19
- // grouping: boolean;
20
18
  showSubstitution: boolean;
21
19
  maxSubstitutions: number;
22
20
  activityLimit: number;
@@ -28,29 +26,25 @@ export class SARViewerBase extends DG.JsViewer {
28
26
  super();
29
27
 
30
28
  this.scaling = this.string('scaling', 'none', {choices: ['none', 'lg', '-lg']});
31
- // this.filterMode = this.bool('filterMode', false);
32
29
  this.bidirectionalAnalysis = this.bool('bidirectionalAnalysis', false);
33
- // this.grouping = this.bool('grouping', false);
34
-
35
- this.showSubstitution = this.bool('showSubstitution', false);
30
+ this.showSubstitution = this.bool('showSubstitution', true);
36
31
  this.maxSubstitutions = this.int('maxSubstitutions', 1);
37
32
  this.activityLimit = this.float('activityLimit', 2);
38
33
  }
39
34
 
40
- async onTableAttached() {
35
+ async onTableAttached(): Promise<void> {
41
36
  super.onTableAttached();
42
37
  this.dataFrame.temp[this.tempName] ??= this;
43
38
  this.sourceGrid = this.view?.grid ?? (grok.shell.v as DG.TableView).grid;
44
- this.controller = await PeptidesController.getInstance(this.dataFrame);
45
- this.controller.init(this.dataFrame);
39
+ this.model = await PeptidesModel.getInstance(this.dataFrame);
40
+ // this.model.init(this.dataFrame);
46
41
  await this.requestDataUpdate();
47
-
48
- // this.subs.push(this.controller.onGroupMappingChanged.subscribe(() => {this.render(true);}));
42
+ this.helpUrl = '/help/domains/bio/peptides.md';
49
43
  }
50
44
 
51
- detach() {this.subs.forEach((sub) => sub.unsubscribe());}
45
+ detach(): void {this.subs.forEach((sub) => sub.unsubscribe());}
52
46
 
53
- render(refreshOnly = false) {
47
+ render(refreshOnly = false): void {
54
48
  if (!this.initialized)
55
49
  return;
56
50
  if (!refreshOnly) {
@@ -62,24 +56,26 @@ export class SARViewerBase extends DG.JsViewer {
62
56
  this.viewerGrid?.invalidate();
63
57
  }
64
58
 
65
- async requestDataUpdate() {
66
- await this.controller.updateData(this.scaling, this.sourceGrid, this.bidirectionalAnalysis,
59
+ async requestDataUpdate(): Promise<void> {
60
+ await this.model.updateData(this.scaling, this.sourceGrid, this.bidirectionalAnalysis,
67
61
  this.activityLimit, this.maxSubstitutions, this.showSubstitution);
68
62
  }
69
63
 
70
- async onPropertyChanged(property: DG.Property) {
64
+ async onPropertyChanged(property: DG.Property): Promise<void> {
71
65
  super.onPropertyChanged(property);
72
66
  this.dataFrame.tags[property.name] = `${property.get(this)}`;
73
67
  if (!this.initialized || IS_PROPERTY_CHANGING)
74
68
  return;
75
-
69
+
76
70
  const propName = property.name;
77
71
 
78
72
  if (propName === 'scaling' && typeof this.dataFrame !== 'undefined') {
79
- const minActivity = this.dataFrame.getCol(C.COLUMNS_NAMES.ACTIVITY).stats.min;
73
+ const activityCol = this.dataFrame.columns.bySemType(C.SEM_TYPES.ACTIVITY)!;
74
+ // const minActivity = this.dataFrame.getCol(C.COLUMNS_NAMES.ACTIVITY).stats.min;
75
+ const minActivity = activityCol.stats.min;
80
76
  if (minActivity && minActivity <= 0 && this.scaling !== 'none') {
81
77
  grok.shell.warning(`Could not apply ${this.scaling}: ` +
82
- `activity column ${C.COLUMNS_NAMES.ACTIVITY} contains zero or negative values, falling back to 'none'.`);
78
+ `activity column ${activityCol.name} contains zero or negative values, falling back to 'none'.`);
83
79
  property.set(this, 'none');
84
80
  return;
85
81
  }
@@ -101,15 +97,16 @@ export class SARViewer extends SARViewerBase {
101
97
  _name = 'Structure-Activity Relationship';
102
98
  tempName = 'sarViewer';
103
99
 
104
- constructor() { super(); }
100
+ constructor() {super();}
105
101
 
106
- get name() {return this._name;}
102
+ get name(): string {return this._name;}
107
103
 
108
- async onTableAttached() {
104
+ async onTableAttached(): Promise<void> {
109
105
  await super.onTableAttached();
110
- this.viewerGrid = this.controller.sarGrid;
106
+ this.viewerGrid = this.model._sarGrid;
107
+ this.dataFrame.temp['sarViewer'] = this;
111
108
 
112
- this.subs.push(this.controller.onSARGridChanged.subscribe((data) => {
109
+ this.subs.push(this.model.onSARGridChanged.subscribe((data) => {
113
110
  this.viewerGrid = data;
114
111
  this.render();
115
112
  }));
@@ -118,22 +115,21 @@ export class SARViewer extends SARViewerBase {
118
115
  this.render();
119
116
  }
120
117
 
121
- isInitialized() { return this.controller?.sarGrid ?? false; }
118
+ isInitialized(): DG.Grid {return this.model?._sarGrid;}
122
119
 
120
+ //1. debouncing in rxjs; 2. flags?
123
121
  async onPropertyChanged(property: DG.Property): Promise<void> {
124
122
  if (!this.isInitialized() || IS_PROPERTY_CHANGING)
125
123
  return;
126
124
 
127
125
  await super.onPropertyChanged(property);
128
126
  IS_PROPERTY_CHANGING = true;
129
- this.controller.syncProperties(true);
127
+ this.model.syncProperties(true);
130
128
  IS_PROPERTY_CHANGING = false;
131
129
  }
132
130
  }
133
131
 
134
- /**
135
- * Vertical structure activity relationship viewer.
136
- */
132
+ /** Vertical structure activity relationship viewer. */
137
133
  export class SARViewerVertical extends SARViewerBase {
138
134
  _name = 'Sequence-Activity relationship';
139
135
  _titleHost = ui.divText('Most Potent Residues', {id: 'pep-viewer-title'});
@@ -143,13 +139,14 @@ export class SARViewerVertical extends SARViewerBase {
143
139
  super();
144
140
  }
145
141
 
146
- get name() {return this._name;}
142
+ get name(): string {return this._name;}
147
143
 
148
- async onTableAttached() {
144
+ async onTableAttached(): Promise<void> {
149
145
  await super.onTableAttached();
150
- this.viewerGrid = this.controller.sarVGrid;
146
+ this.viewerGrid = this.model._sarVGrid;
147
+ this.dataFrame.temp['sarViewerVertical'] = this;
151
148
 
152
- this.subs.push(this.controller.onSARVGridChanged.subscribe((data) => {
149
+ this.subs.push(this.model.onSARVGridChanged.subscribe((data) => {
153
150
  this.viewerGrid = data;
154
151
  this.render();
155
152
  }));
@@ -158,7 +155,7 @@ export class SARViewerVertical extends SARViewerBase {
158
155
  this.render();
159
156
  }
160
157
 
161
- isInitialized() { return this.controller?.sarVGrid ?? false; }
158
+ isInitialized(): DG.Grid {return this.model?._sarVGrid;}
162
159
 
163
160
  async onPropertyChanged(property: DG.Property): Promise<void> {
164
161
  if (!this.isInitialized() || IS_PROPERTY_CHANGING)
@@ -166,7 +163,7 @@ export class SARViewerVertical extends SARViewerBase {
166
163
 
167
164
  await super.onPropertyChanged(property);
168
165
  IS_PROPERTY_CHANGING = true;
169
- this.controller.syncProperties(false);
166
+ this.model.syncProperties(false);
170
167
  IS_PROPERTY_CHANGING = false;
171
168
  }
172
169
  }
@@ -2,28 +2,21 @@ import * as DG from 'datagrok-api/dg';
2
2
  import * as rxjs from 'rxjs';
3
3
  import * as ui from 'datagrok-api/ui';
4
4
  import {MonomerLibrary} from '../monomer-library';
5
- import {PeptidesController} from '../peptides';
6
5
 
7
6
  import * as C from '../utils/constants';
8
7
  import * as type from '../utils/types';
8
+ import {PeptidesModel} from '../model';
9
9
 
10
- export function addViewerToHeader(grid: DG.Grid, barchart: StackedBarChart) {
10
+ export function addViewerToHeader(grid: DG.Grid, barchart: StackedBarChart): void {
11
11
  if (grid.temp['containsBarchart'])
12
12
  return;
13
-
14
- const compareBarParts = (bar1: type.BarChart.BarPart | null, bar2: type.BarChart.BarPart | null) =>
15
- bar1 && bar2 && bar1.aaName === bar2.aaName && bar1.colName === bar2.colName;
16
13
 
17
- const eventAction = (mouseMove: MouseEvent) => {
18
- const cell = grid.hitTest(mouseMove.offsetX, mouseMove.offsetY);
14
+ const eventAction = (ev: MouseEvent): void => {
15
+ const cell = grid.hitTest(ev.offsetX, ev.offsetY);
19
16
  if (cell?.isColHeader && cell.tableColumn?.semType == C.SEM_TYPES.AMINO_ACIDS) {
20
- const newBarPart = barchart.findAARandPosition(cell, mouseMove);
21
- const previousClickedBarPart = barchart._previousClickedBarPart;
22
- if (mouseMove.type === 'click' && compareBarParts(newBarPart, previousClickedBarPart))
23
- barchart.isSameBarClicked = true;
24
- else
25
- barchart.currentBarPart = newBarPart;
26
- barchart.requestAction(mouseMove);
17
+ const newBarPart = barchart.findAARandPosition(cell, ev);
18
+ barchart._currentBarPart = newBarPart;
19
+ barchart.requestAction(ev, newBarPart);
27
20
  barchart.computeData();
28
21
  }
29
22
  };
@@ -45,10 +38,10 @@ export function addViewerToHeader(grid: DG.Grid, barchart: StackedBarChart) {
45
38
  ) {
46
39
  if (!cell.isColHeader) {
47
40
  const monomerLib = cell.cell.dataFrame.temp[MonomerLibrary.id];
48
- PeptidesController.chemPalette.showTooltip(cell, x, y, monomerLib);
49
- } else if (barchart.currentBarPart) {
41
+ PeptidesModel.chemPalette.showTooltip(cell, x, y, monomerLib);
42
+ } else if (barchart._currentBarPart) {
50
43
  let elements: HTMLElement[] = [];
51
- elements = elements.concat([ui.divText(barchart.currentBarPart.aaName)]);
44
+ elements = elements.concat([ui.divText(barchart._currentBarPart.aaName)]);
52
45
  ui.tooltip.show(ui.divV(elements), x, y);
53
46
  }
54
47
  }
@@ -92,22 +85,14 @@ export class StackedBarChart extends DG.JsViewer {
92
85
  barStats: {[Key: string]: type.BarChart.BarStatsObject[]} = {};
93
86
  selected: type.BarChart.BarPart[] = [];
94
87
  aggregatedSelectedTables: type.DataFrameDict = {};
95
- controller!: PeptidesController;
96
- isSameBarClicked: boolean = false;
97
- _previousClickedBarPart: type.BarChart.BarPart | null = null;
88
+ model!: PeptidesModel;
98
89
 
99
90
  constructor() {
100
91
  super();
101
92
  this.dataEmptyAA = this.string('dataEmptyAA', '-');
102
93
  }
103
94
 
104
- get currentBarPart() { return this._currentBarPart; }
105
- set currentBarPart(barPart: type.BarChart.BarPart | null) {
106
- this._currentBarPart = barPart;
107
- this.isSameBarClicked = false;
108
- }
109
-
110
- init() {
95
+ init(): void {
111
96
  const groups: {[key: string]: string[]} = {
112
97
  'yellow': ['C', 'U'],
113
98
  'red': ['G', 'P'],
@@ -127,10 +112,10 @@ export class StackedBarChart extends DG.JsViewer {
127
112
  }
128
113
 
129
114
  // Stream subscriptions
130
- async onTableAttached() {
115
+ async onTableAttached(): Promise<void> {
131
116
  this.init();
132
- this.controller = await PeptidesController.getInstance(this.dataFrame);
133
- this.controller.init(this.dataFrame);
117
+ this.model = await PeptidesModel.getInstance(this.dataFrame);
118
+ // this.controller.init(this.dataFrame);
134
119
  if (this.dataFrame) {
135
120
  this.subs.push(DG.debounce(this.dataFrame.selection.onChanged, 50).subscribe((_) => this.computeData()));
136
121
  this.subs.push(DG.debounce(this.dataFrame.filter.onChanged, 50).subscribe((_) => this.computeData()));
@@ -139,18 +124,18 @@ export class StackedBarChart extends DG.JsViewer {
139
124
  }
140
125
 
141
126
  // Cancel subscriptions when the viewer is detached
142
- detach() {
127
+ detach(): void {
143
128
  this.subs.forEach((sub) => sub.unsubscribe());
144
129
  }
145
130
 
146
- computeData() {
131
+ computeData(): void {
147
132
  this.aminoColumnNames = [];
148
133
  this.aminoColumnIndices = {};
149
134
 
150
- this.dataFrame!.columns.names().forEach((name: string) => {
151
- if (this.dataFrame!.getCol(name).semType === C.SEM_TYPES.AMINO_ACIDS &&
152
- !this.dataFrame!.getCol(name).categories.includes('COOH') &&
153
- !this.dataFrame!.getCol(name).categories.includes('NH2')) {
135
+ this.dataFrame.columns.names().forEach((name: string) => {
136
+ if (this.dataFrame.getCol(name).semType === C.SEM_TYPES.AMINO_ACIDS &&
137
+ !this.dataFrame.getCol(name).categories.includes('COOH') &&
138
+ !this.dataFrame.getCol(name).categories.includes('NH2')) {
154
139
  this.aminoColumnIndices[name] = this.aminoColumnNames.length + 1;
155
140
  this.aminoColumnNames.push(name);
156
141
  }
@@ -160,15 +145,15 @@ export class StackedBarChart extends DG.JsViewer {
160
145
  this.aggregatedSelectedTables = {};
161
146
  //TODO: optimize it, why store so many tables?
162
147
  this.aminoColumnNames.forEach((name) => {
163
- this.aggregatedFilterTables[name] = this.dataFrame!
148
+ this.aggregatedFilterTables[name] = this.dataFrame
164
149
  .groupBy([name])
165
- .whereRowMask(this.dataFrame!.filter)
150
+ .whereRowMask(this.dataFrame.filter)
166
151
  .add('count', name, `${name}_count`)
167
152
  .aggregate();
168
153
 
169
- this.aggregatedSelectedTables[name] = this.dataFrame!
154
+ this.aggregatedSelectedTables[name] = this.dataFrame
170
155
  .groupBy([name])
171
- .whereRowMask(this.dataFrame!.selection)
156
+ .whereRowMask(this.dataFrame.selection)
172
157
  .add('count', name, `${name}_count`)
173
158
  .aggregate();
174
159
  });
@@ -206,10 +191,10 @@ export class StackedBarChart extends DG.JsViewer {
206
191
  colData.sort((o1, o2) => this.ord[o2['name']] - this.ord[o1['name']]);
207
192
  }
208
193
 
209
- this.max = this.dataFrame!.filter.trueCount;
194
+ this.max = this.dataFrame.filter.trueCount;
210
195
  }
211
196
 
212
- renderBarToCanvas(g: CanvasRenderingContext2D, cell: DG.GridCell, x: number, y: number, w: number, h: number) {
197
+ renderBarToCanvas(g: CanvasRenderingContext2D, cell: DG.GridCell, x: number, y: number, w: number, h: number): void {
213
198
  const name = cell.tableColumn!.name;
214
199
  const colNameSize = g.measureText(name).width;
215
200
  const barData = this.barStats[name];
@@ -236,7 +221,7 @@ export class StackedBarChart extends DG.JsViewer {
236
221
  const sBarHeight = h * obj['count'] / this.max;
237
222
  const gapSize = sBarHeight * innerMargin;
238
223
  const verticalShift = (this.max - sum) / this.max;
239
- const [color, aarOuter] = PeptidesController.chemPalette.getColorAAPivot(obj['name']);
224
+ const [color, aarOuter] = PeptidesModel.chemPalette.getColorAAPivot(obj['name']);
240
225
  const textSize = g.measureText(aarOuter);
241
226
  const fontSize = 11;
242
227
  const leftMargin = (w - (aarOuter.length > 1 ? fontSize : textSize.width - 8)) / 2;
@@ -252,7 +237,7 @@ export class StackedBarChart extends DG.JsViewer {
252
237
  if (textSize.width <= subBartHeight) {
253
238
  const origTransform = g.getTransform();
254
239
 
255
- if (color != PeptidesController.chemPalette.undefinedColor) {
240
+ if (color != PeptidesModel.chemPalette.undefinedColor) {
256
241
  g.fillRect(x + xStart, y + yStart, barWidth, subBartHeight);
257
242
  g.fillStyle = 'black';
258
243
  } else
@@ -287,7 +272,7 @@ export class StackedBarChart extends DG.JsViewer {
287
272
  });
288
273
  }
289
274
 
290
- findAARandPosition(cell: DG.GridCell, mouseEvent: MouseEvent) {
275
+ findAARandPosition(cell: DG.GridCell, mouseEvent: MouseEvent): {colName: string, aaName: string} | null {
291
276
  if (!cell.tableColumn?.name || !this.aminoColumnNames.includes(cell.tableColumn.name))
292
277
  return null;
293
278
 
@@ -330,28 +315,20 @@ export class StackedBarChart extends DG.JsViewer {
330
315
  return null;
331
316
  }
332
317
 
333
- unhighlight() {
318
+ unhighlight(): void {
334
319
  ui.tooltip.hide();
335
320
  this.computeData();
336
321
  }
337
322
 
338
- /**
339
- * Requests highlight/select/filter action based on currentBarPart
340
- * @param event
341
- * @returns
342
- */
343
- requestAction(event: MouseEvent) {
344
- if (!this._currentBarPart)
323
+ /** Requests highlight/select/filter action based on currentBarPart */
324
+ requestAction(event: MouseEvent, barPart: {colName: string, aaName: string} | null): void {
325
+ if (!barPart)
345
326
  return;
346
- let aar = this._currentBarPart!['aaName'];
347
- let position = this._currentBarPart!['colName'];
327
+ const aar = barPart['aaName'];
328
+ const position = barPart['colName'];
348
329
  if (event.type === 'click') {
349
- if (this.isSameBarClicked) {
350
- aar = position = C.CATEGORIES.ALL;
351
- this.currentBarPart = null;
352
- }
353
- this.controller.setSARGridCellAt(aar, position);
354
- this._previousClickedBarPart = this._currentBarPart;
330
+ event.shiftKey ? this.model.modifyCurrentSelection(aar, position) :
331
+ this.model.initCurrentSelection(aar, position);
355
332
  } else {
356
333
  ui.tooltip.showRowGroup(this.dataFrame, (i) => {
357
334
  const currentAAR = this.dataFrame.get(position, i);