@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/CHANGELOG.md +15 -0
- package/dist/package-test.js +1 -1
- package/dist/package-test.js.map +1 -1
- package/dist/package.js +1 -1
- package/dist/package.js.map +1 -1
- package/package.json +5 -5
- package/src/cell-renderer.ts +34 -18
- package/src/helm-monomer-placer.ts +51 -26
- package/src/package-test.ts +1 -0
- package/src/tests/get-all-parts-tests.ts +34 -0
- package/src/tests/properties-widget-tests.ts +19 -1
- package/src/utils/index.ts +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datagrok/helm",
|
|
3
3
|
"friendlyName": "Helm",
|
|
4
|
-
"version": "2.1.
|
|
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.
|
|
19
|
-
"@datagrok-libraries/chem-meta": "^1.2.
|
|
20
|
-
"@datagrok-libraries/utils": "^4.1
|
|
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.
|
|
40
|
+
"@datagrok/bio": "^2.12.11",
|
|
41
41
|
"@datagrok/chem": "^1.9.0"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
package/src/cell-renderer.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
55
|
-
|
|
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
|
-
|
|
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.
|
|
121
|
-
this.
|
|
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
|
+
}
|
package/src/package-test.ts
CHANGED
|
@@ -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);
|
package/src/utils/index.ts
CHANGED
|
@@ -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);
|