@datagrok/helm 2.1.31 → 2.1.33

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@datagrok/helm",
3
3
  "friendlyName": "Helm",
4
- "version": "2.1.31",
4
+ "version": "2.1.33",
5
5
  "author": {
6
6
  "name": "Oleksandra Serhiienko",
7
7
  "email": "oserhiienko@datagrok.ai"
@@ -15,9 +15,9 @@
15
15
  "helm/JSDraw/Pistoia.HELM-uncompressed.js"
16
16
  ],
17
17
  "dependencies": {
18
- "@datagrok-libraries/bio": "^5.40.0",
19
- "@datagrok-libraries/chem-meta": "^1.2.3",
20
- "@datagrok-libraries/utils": "^4.1.45",
18
+ "@datagrok-libraries/bio": "^5.40.8",
19
+ "@datagrok-libraries/chem-meta": "^1.2.5",
20
+ "@datagrok-libraries/utils": "^4.2.1",
21
21
  "cash-dom": "^8.1.1",
22
22
  "datagrok-api": "^1.17.0",
23
23
  "dayjs": "^1.10.6",
@@ -37,7 +37,7 @@
37
37
  "@typescript-eslint/parser": "^5.11.0",
38
38
  "eslint": "^8.18.0",
39
39
  "eslint-config-google": "^0.14.0",
40
- "@datagrok/bio": "^2.12.0",
40
+ "@datagrok/bio": "^2.12.11",
41
41
  "@datagrok/chem": "^1.9.0"
42
42
  },
43
43
  "scripts": {
@@ -8,7 +8,7 @@ import {printLeftOrCentered} from '@datagrok-libraries/bio/src/utils/cell-render
8
8
  import {errorToConsole} from '@datagrok-libraries/utils/src/to-console';
9
9
 
10
10
  import {findMonomers, parseHelm, removeGapsFromHelm} from './utils';
11
- import {IEditor, HelmMonomerPlacer, IEditorMolAtom, ISeqMonomer} from './helm-monomer-placer';
11
+ import {IEditor, HelmMonomerPlacer, IEditorMolAtom, ISeqMonomer, Temps} from './helm-monomer-placer';
12
12
  import {IMonomerLib} from '@datagrok-libraries/bio/src/types/index';
13
13
 
14
14
  // import {_package} from './package'; // NullError: method not found: '_package' on null
@@ -29,7 +29,7 @@ function getSeqMonomerFromHelm(
29
29
  resSeqMonomer = {symbol: symbol, polymerType: polymerType};
30
30
  }
31
31
  if (!resSeqMonomer)
32
- resSeqMonomer = {symbol: symbol};
32
+ throw new Error(`Monomer not found for symbol = '${symbol}' and helmPrefix = '${helmPrefix}'.`);
33
33
  return resSeqMonomer;
34
34
  }
35
35
 
@@ -139,6 +139,28 @@ function getHoveredMonomerFallback(
139
139
  return hoveredSeqMonomer;
140
140
  }
141
141
 
142
+ type RendererGridCellTemp = {
143
+ [Temps.helmMonomerPlacer]: HelmMonomerPlacer;
144
+ }
145
+
146
+ function getRendererGridCellTemp(gridCell: DG.GridCell
147
+ ): [DG.GridColumn | null, DG.Column | null, RendererGridCellTemp] {
148
+ /** Primarily store/get MonomerPlacer at GridColumn, fallback at (Table) Column for scatter plot tooltip */
149
+ let temp: RendererGridCellTemp | null = null;
150
+
151
+ let gridCol: DG.GridColumn | null = null;
152
+ try { gridCol = gridCell.gridColumn; } catch { gridCol = null; }
153
+ temp = gridCol ? gridCol.temp as RendererGridCellTemp : null;
154
+
155
+ let tableCol: DG.Column | null = null;
156
+ if (!temp) {
157
+ try { tableCol = gridCell.cell.column; } catch { tableCol = null; }
158
+ temp = tableCol ? tableCol.temp as RendererGridCellTemp : null;
159
+ }
160
+ if (temp === null) throw new Error(`Monomer placer store (GridColumn or Column) not found.`);
161
+ return [gridCol, tableCol, temp];
162
+ }
163
+
142
164
  /** Helm cell renderer in case of no missed monomer draws with JSDraw2.Editor (webeditor),
143
165
  * in case of missed monomers presented, draws linear sequences aligned in width per monomer.
144
166
  */
@@ -153,16 +175,15 @@ export class HelmCellRenderer extends DG.GridCellRenderer {
153
175
 
154
176
  onMouseMove(gridCell: DG.GridCell, e: MouseEvent): void {
155
177
  try {
156
- /* Can not do anything without tableColumn containing temp */
157
- let tableCol: DG.Column | null = null;
158
- try { tableCol = gridCell.tableColumn; } catch { }
178
+ const [_gridCol, tableCol, temp] = getRendererGridCellTemp(gridCell);
179
+ const helmPlacer = temp[Temps.helmMonomerPlacer];
180
+ /* Can not do anything without tableColumn */
159
181
  if (!tableCol) return;
160
182
 
161
183
  /** {@link gridCell}.bounds */ const gcb = gridCell.bounds;
162
184
  const argsX = e.offsetX - gcb.x;
163
185
  const argsY = e.offsetY - gcb.y;
164
186
 
165
- const helmPlacer = HelmMonomerPlacer.getOrCreate(tableCol);
166
187
  const editor: IEditor | null = helmPlacer.getEditor(gridCell.tableRowIndex!);
167
188
  let seqMonomer: ISeqMonomer | null;
168
189
  let missedMonomers: Set<string> = new Set<string>(); // of .size = 0
@@ -189,15 +210,10 @@ export class HelmCellRenderer extends DG.GridCellRenderer {
189
210
 
190
211
  if (seqMonomer) {
191
212
  if (!missedMonomers.has(seqMonomer.symbol)) {
192
- const monomer = helmPlacer.getMonomer(seqMonomer);
193
- if (monomer) {
194
- const options = {autoCrop: true, autoCropMargin: 0, suppressChiralText: true};
195
- const monomerSvg = grok.chem.svgMol(monomer.smiles, undefined, undefined, options);
196
- ui.tooltip.show(ui.divV([
197
- ui.divText(seqMonomer.symbol),
198
- monomerSvg,
199
- ]), e.x + 16, e.y + 16);
200
- }
213
+ const tooltipElements: HTMLElement[] = [ui.div(seqMonomer.symbol)];
214
+ const monomerDiv = helmPlacer.monomerLib.getTooltip(seqMonomer.polymerType, seqMonomer.symbol);
215
+ tooltipElements.push(monomerDiv);
216
+ ui.tooltip.show(ui.divV(tooltipElements), e.x + 16, e.y + 16);
201
217
  } else {
202
218
  ui.tooltip.show(ui.divV([
203
219
  ui.divText(`Monomer '${seqMonomer.symbol}' not found.`),
@@ -230,10 +246,11 @@ export class HelmCellRenderer extends DG.GridCellRenderer {
230
246
  ) {
231
247
  g.save();
232
248
  try {
249
+ const [gridCol, tableCol, temp] = getRendererGridCellTemp(gridCell);
233
250
  /* Can not do anything without tableColumn containing temp */
234
- let tableCol: DG.Column | null = null;
235
- try { tableCol = gridCell.tableColumn; } catch { }
236
251
  if (!tableCol) return;
252
+ let helmPlacer = temp[Temps.helmMonomerPlacer];
253
+ if (!helmPlacer) helmPlacer = temp[Temps.helmMonomerPlacer] = new HelmMonomerPlacer(gridCol, tableCol);
237
254
 
238
255
  const grid = gridCell.gridRow !== -1 ? gridCell.grid : undefined;
239
256
  const missedColor = 'red';
@@ -244,7 +261,6 @@ export class HelmCellRenderer extends DG.GridCellRenderer {
244
261
  const monomerList = parseHelm(seq);
245
262
  const monomers: Set<string> = new Set<string>(monomerList);
246
263
  const missedMonomers: Set<string> = findMonomers(monomerList);
247
- const helmPlacer = HelmMonomerPlacer.getOrCreate(tableCol);
248
264
 
249
265
  if (missedMonomers.size == 0) {
250
266
  // Recreate host to avoid hanging in window.dojox.gfx.svg.Text.prototype.getTextWidth
@@ -3,11 +3,13 @@ import * as ui from 'datagrok-api/ui';
3
3
  import * as DG from 'datagrok-api/dg';
4
4
 
5
5
  import wu from 'wu';
6
+ import {Unsubscribable} from 'rxjs';
6
7
 
7
8
  import {IMonomerLib, Monomer} from '@datagrok-libraries/bio/src/types/index';
8
9
 
9
10
  import {getParts, parseHelm} from './utils';
10
- import {getMonomerLib} from './package';
11
+
12
+ import {_package, getMonomerLib} from './package';
11
13
 
12
14
  export const enum Temps {
13
15
  helmMonomerPlacer = 'bio-helmMonomerPlacer',
@@ -36,31 +38,60 @@ export interface IEditorPoint {
36
38
  }
37
39
 
38
40
  export interface ISeqMonomer {
41
+ polymerType: string
39
42
  symbol: string,
40
- polymerType?: string
41
43
  }
42
44
 
43
45
  export class HelmMonomerPlacer {
44
- private readonly _allPartsList: (string[] | null)[];
45
- private readonly _lengthsList: (number[] | null)[];
46
- private readonly _editorList: (IEditor | null)[];
47
-
46
+ private readonly grid: DG.Grid | null;
48
47
  public readonly monomerLib: IMonomerLib;
49
48
 
49
+ private _allPartsList: (string[] | null)[];
50
+ private _lengthsList: (number[] | null)[];
51
+ private _editorList: (IEditor | null)[];
52
+
50
53
  public monomerCharWidth: number = 7;
51
54
  public leftPadding: number = 5;
52
55
  public monomerTextSizeMap: { [p: string]: TextMetrics } = {};
53
56
 
54
- constructor(public readonly col: DG.Column<string>) {
55
- this.col.dataFrame.onDataChanged.subscribe();
57
+ private subs: Unsubscribable[] = [];
58
+
59
+ public constructor(
60
+ public readonly gridCol: DG.GridColumn | null,
61
+ public readonly col: DG.Column<string>
62
+ ) {
63
+ this.grid = this.gridCol ? this.gridCol.grid : null;
64
+ this.reset();
65
+ if (this.grid) {
66
+ this.subs.push(this.col.dataFrame.onDataChanged.subscribe(() => {
67
+ try {
68
+ this.reset();
69
+ } catch (err: any) {
70
+ console.error(err);
71
+ }
72
+ }));
73
+ this.subs.push(grok.events.onViewRemoved.subscribe((view: DG.View) => {
74
+ try {
75
+ if (this.grid && this.grid.view?.id === view.id) this.destroy();
76
+ } catch (err: any) {
77
+ console.error(err);
78
+ }
79
+ }));
80
+ }
56
81
  this.monomerLib = getMonomerLib();
57
- this.monomerLib.onChanged.subscribe(this.monomerLibOnChanged.bind(this));
82
+ this.subs.push(this.monomerLib.onChanged.subscribe(this.monomerLibOnChanged.bind(this)));
83
+ }
58
84
 
85
+ private reset(): void {
59
86
  this._allPartsList = new Array<string[] | null>(this.col.length).fill(null);
60
87
  this._lengthsList = new Array<number[] | null>(this.col.length).fill(null);
61
88
  this._editorList = new Array<IEditor | null>(this.col.length).fill(null);
62
89
  }
63
90
 
91
+ private destroy(): void {
92
+ for (const sub of this.subs) sub.unsubscribe();
93
+ }
94
+
64
95
  /** Skips cell for the fallback rendering */
65
96
  public skipCell(rowIdx: number): void {
66
97
  this._allPartsList[rowIdx] = [];
@@ -80,8 +111,10 @@ export class HelmMonomerPlacer {
80
111
 
81
112
  private getCellMonomerLengthsForSeq(rowIdx: number): [string[], number[]] {
82
113
  let allParts: string[] | null = this._allPartsList![rowIdx];
83
- if (allParts === null)
84
- allParts = this._allPartsList![rowIdx] = this.getAllParts(rowIdx);
114
+ if (allParts === null) {
115
+ const seq = this.col.get(rowIdx);
116
+ allParts = this._allPartsList![rowIdx] = getAllParts(seq);
117
+ }
85
118
 
86
119
  let lengths: number[] | null = this._lengthsList![rowIdx];
87
120
  if (lengths === null) {
@@ -95,12 +128,6 @@ export class HelmMonomerPlacer {
95
128
  return [allParts, lengths];
96
129
  }
97
130
 
98
- getAllParts(rowIdx: number): string[] {
99
- const seq: string | null = this.col.get(rowIdx);
100
- const monomerList: string[] = seq ? parseHelm(seq) : [];
101
- return seq ? getParts(monomerList, seq) : [];
102
- }
103
-
104
131
  getMonomer(monomer: ISeqMonomer): Monomer | null {
105
132
  let res: Monomer | null = null;
106
133
  if (monomer.polymerType)
@@ -117,15 +144,8 @@ export class HelmMonomerPlacer {
117
144
  // -- Handle events --
118
145
 
119
146
  private monomerLibOnChanged(_value: any): void {
120
- this._lengthsList.fill(null);
121
- this._allPartsList.fill(null);
122
- this._editorList.fill(null);
123
- // TODO: Invalidate all grids of this.col.dataFrame
124
- }
125
-
126
- public static getOrCreate(col: DG.Column<string>): HelmMonomerPlacer {
127
- if (!(Temps.helmMonomerPlacer in col.temp)) col.temp[Temps.helmMonomerPlacer] = new HelmMonomerPlacer(col);
128
- return col.temp[Temps.helmMonomerPlacer];
147
+ this.reset();
148
+ if (this.grid) this.grid.invalidate();
129
149
  }
130
150
 
131
151
  public setEditor(tableRowIndex: number, editor: IEditor) {
@@ -136,3 +156,8 @@ export class HelmMonomerPlacer {
136
156
  return this._editorList![tableRowIndex];
137
157
  }
138
158
  }
159
+
160
+ export function getAllParts(seq: string | null): string[] {
161
+ const monomerList: string[] = !!seq ? parseHelm(seq) : [];
162
+ return !!seq ? getParts(monomerList, seq) : [];
163
+ }
@@ -8,6 +8,7 @@ import './tests/findMonomers-tests';
8
8
  import './tests/renderers-tests';
9
9
  import './tests/get-molfiles';
10
10
  import './tests/properties-widget-tests';
11
+ import './tests/get-all-parts-tests';
11
12
 
12
13
  export const _package = new DG.Package();
13
14
  export {tests};
@@ -0,0 +1,34 @@
1
+ import * as grok from 'datagrok-api/grok';
2
+ import * as ui from 'datagrok-api/ui';
3
+ import * as DG from 'datagrok-api/dg';
4
+
5
+ import {after, before, category, delay, expect, test, expectArray} from '@datagrok-libraries/utils/src/test';
6
+
7
+ import {getAllParts} from '../helm-monomer-placer';
8
+
9
+ category('getAllParts', () => {
10
+ /** All parts of Helm string are required for fallback rendering. */
11
+
12
+ const tests: { [testName: string]: { tgt: string[]; seq: string } } = {
13
+ 'peptideSimpleSingleChar': {
14
+ seq: 'PEPTIDE1{L.V.A}$$$$',
15
+ tgt: ['PEPTIDE1{', 'L', '.', 'V', '.', 'A', '}$$$$'],
16
+ },
17
+ 'peptideSimpleMultiChar': {
18
+ seq: 'PEPTIDE1{[Ac].V.A}$$$$',
19
+ /* doubt joining square brackets to separator or notation is correct */
20
+ tgt: ['PEPTIDE1{[', 'Ac', '].', 'V', '.', 'A', '}$$$$'],
21
+ }
22
+ };
23
+
24
+ for (const [testName, testData] of Object.entries(tests)) {
25
+ test(`${testName}`, async () => {
26
+ await _testAllParts(testData.seq, testData.tgt);
27
+ });
28
+ }
29
+ });
30
+
31
+ async function _testAllParts(seq: string | null, tgt: string[]): Promise<void> {
32
+ const res: string[] = getAllParts(seq);
33
+ expectArray(res, tgt);
34
+ }
@@ -2,9 +2,11 @@ import * as ui from 'datagrok-api/ui';
2
2
  import * as DG from 'datagrok-api/dg';
3
3
  import * as grok from 'datagrok-api/grok';
4
4
 
5
- import {category, expect, expectObject, test} from '@datagrok-libraries/utils/src/test';
5
+ import {after, before, category, expect, expectObject, test} from '@datagrok-libraries/utils/src/test';
6
6
  import {ALPHABET, NOTATION, TAGS as bioTAGS} from '@datagrok-libraries/bio/src/utils/macromolecule';
7
7
  import {SeqHandler} from '@datagrok-libraries/bio/src/utils/seq-handler';
8
+ import {getUserLibSettings, setUserLibSettings, setUserLibSettingsForTests} from '@datagrok-libraries/bio/src/monomer-works/lib-settings';
9
+ import {getMonomerLibHelper, IMonomerLibHelper} from '@datagrok-libraries/bio/src/monomer-works/monomer-utils';
8
10
 
9
11
  import {getPropertiesDict} from '../widgets/properties-widget';
10
12
 
@@ -60,6 +62,22 @@ const TestsData: {
60
62
  };
61
63
 
62
64
  category('properties-widget', () => {
65
+ let monomerLibHelper: IMonomerLibHelper;
66
+ /** Backup actual user's monomer libraries settings */
67
+ let userLibSettings: any = null;
68
+
69
+ before(async () => {
70
+ monomerLibHelper = await getMonomerLibHelper();
71
+ userLibSettings = getUserLibSettings();
72
+ await setUserLibSettingsForTests();
73
+ await monomerLibHelper.loadLibraries(true); // load default libraries
74
+ });
75
+
76
+ after(async () => {
77
+ await setUserLibSettings(userLibSettings);
78
+ await monomerLibHelper.loadLibraries(true); // load user settings libraries
79
+ });
80
+
63
81
  for (const [testName, testData] of Object.entries(TestsData)) {
64
82
  test(testName, async () => {
65
83
  testPropertiesDict(testData.seq, testData.units, testData.separator, testData.alphabet, testData.res);
@@ -24,7 +24,7 @@ export function removeGapsFromHelm(srcHelm: string): string {
24
24
  export function getParts(subParts: string[], s: string): string[] {
25
25
  const j = 0;
26
26
  const allParts: string[] = [];
27
- for (let k = 0; k < subParts.length; ++k) {
27
+ for (let k = 0; k < (subParts ?? []).length; ++k) {
28
28
  const indexOfMonomer = s.indexOf(subParts[k]);
29
29
  const helmBeforeMonomer = s.slice(j, indexOfMonomer);
30
30
  allParts.push(helmBeforeMonomer);