@datagrok/bio 2.15.8 → 2.15.10
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 +16 -0
- package/css/monomer-manager.css +4 -0
- package/dist/284.js +1 -1
- package/dist/284.js.map +1 -1
- package/dist/package-test.js +3 -3
- package/dist/package-test.js.map +1 -1
- package/dist/package.js +3 -3
- package/dist/package.js.map +1 -1
- package/files/monomer-libraries/sample-lib-Aca-colored.json +2 -2
- package/package.json +6 -6
- package/src/utils/cell-renderer-custom.ts +10 -2
- package/src/utils/cell-renderer.ts +5 -21
- package/src/utils/monomer-cell-renderer.ts +1 -2
- package/src/utils/monomer-lib/monomer-lib-base.ts +91 -5
- package/src/utils/monomer-lib/monomer-manager/monomer-manager.ts +145 -29
- package/src/utils/monomer-lib/web-editor-monomer-of-library.ts +3 -42
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"name": "Aleksandr Tanas",
|
|
6
6
|
"email": "atanas@datagrok.ai"
|
|
7
7
|
},
|
|
8
|
-
"version": "2.15.
|
|
8
|
+
"version": "2.15.10",
|
|
9
9
|
"description": "Bioinformatics support (import/export of sequences, conversion, visualization, analysis). [See more](https://github.com/datagrok-ai/public/blob/master/packages/Bio/README.md) for details.",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
@@ -37,12 +37,12 @@
|
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@biowasm/aioli": "^3.1.0",
|
|
40
|
-
"@datagrok-libraries/bio": "^5.44.
|
|
40
|
+
"@datagrok-libraries/bio": "^5.44.5",
|
|
41
41
|
"@datagrok-libraries/chem-meta": "^1.2.7",
|
|
42
|
-
"@datagrok-libraries/math": "^1.2.
|
|
43
|
-
"@datagrok-libraries/ml": "^6.7.
|
|
44
|
-
"@datagrok-libraries/tutorials": "^1.4.
|
|
45
|
-
"@datagrok-libraries/utils": "^4.3.
|
|
42
|
+
"@datagrok-libraries/math": "^1.2.1",
|
|
43
|
+
"@datagrok-libraries/ml": "^6.7.3",
|
|
44
|
+
"@datagrok-libraries/tutorials": "^1.4.3",
|
|
45
|
+
"@datagrok-libraries/utils": "^4.3.6",
|
|
46
46
|
"@webgpu/types": "^0.1.40",
|
|
47
47
|
"ajv": "^8.12.0",
|
|
48
48
|
"ajv-errors": "^3.0.0",
|
|
@@ -5,6 +5,7 @@ import * as ui from 'datagrok-api/ui';
|
|
|
5
5
|
import {getGridCellColTemp, CellRendererBackBase} from '@datagrok-libraries/bio/src/utils/cell-renderer-back-base';
|
|
6
6
|
import {SeqHandler} from '@datagrok-libraries/bio/src/utils/seq-handler';
|
|
7
7
|
import {NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
8
|
+
import {GridCell} from 'datagrok-api/dg';
|
|
8
9
|
|
|
9
10
|
export class MacromoleculeCustomCellRenderer extends DG.GridCellRenderer {
|
|
10
11
|
get name(): string { return 'sequence'; }
|
|
@@ -24,9 +25,16 @@ export class MacromoleculeCustomCellRenderer extends DG.GridCellRenderer {
|
|
|
24
25
|
return back;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
override
|
|
28
|
+
override onMouseEnter(gridCell: GridCell, e: MouseEvent) {
|
|
28
29
|
const back = this.getRendererBack(gridCell);
|
|
29
|
-
back.
|
|
30
|
+
back.onMouseEnter(gridCell, e);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
override onMouseLeave(gridCell: DG.GridCell, e: MouseEvent) {
|
|
34
|
+
// TODO: We get gridCell from another column here, so we can not get back object from the column rendered.
|
|
35
|
+
ui.tooltip.hide();
|
|
36
|
+
// const back = this.getRendererBack(gridCell);
|
|
37
|
+
// back.onMouseLeave(gridCell, e);
|
|
30
38
|
}
|
|
31
39
|
|
|
32
40
|
override onMouseMove(gridCell: DG.GridCell, e: MouseEvent) {
|
|
@@ -102,10 +102,11 @@ export class MacromoleculeSequenceCellRenderer extends DG.GridCellRenderer {
|
|
|
102
102
|
): void {
|
|
103
103
|
const logPrefix: string = 'MacromoleculeSequenceCellRenderer.render()';
|
|
104
104
|
|
|
105
|
-
const [gridCol, tableCol,
|
|
105
|
+
const [gridCol, tableCol, temp] =
|
|
106
106
|
getGridCellColTemp<string, MonomerPlacer>(gridCell);
|
|
107
107
|
if (!tableCol) return;
|
|
108
108
|
const tableColTemp: TempType = tableCol.temp;
|
|
109
|
+
const sh = SeqHandler.forColumn(tableCol);
|
|
109
110
|
|
|
110
111
|
let gapLength = 0;
|
|
111
112
|
const msaGapLength = 8;
|
|
@@ -121,31 +122,16 @@ export class MacromoleculeSequenceCellRenderer extends DG.GridCellRenderer {
|
|
|
121
122
|
maxLengthOfMonomer = !isNaN(v) && v ? v : 50;
|
|
122
123
|
}
|
|
123
124
|
|
|
124
|
-
const [_gc, _tc, temp] =
|
|
125
|
-
getGridCellColTemp<string, MonomerPlacer>(gridCell);
|
|
126
125
|
let seqColTemp: MonomerPlacer = temp.rendererBack;
|
|
127
126
|
if (!seqColTemp) {
|
|
128
127
|
seqColTemp = temp.rendererBack = new MonomerPlacer(gridCol, tableCol, _package.logger, maxLengthOfMonomer,
|
|
129
128
|
() => {
|
|
130
|
-
const sh = SeqHandler.forColumn(tableCol);
|
|
131
129
|
return {
|
|
132
|
-
seqHandler: sh,
|
|
133
130
|
monomerCharWidth: 7, separatorWidth: !sh.isMsa() ? gapLength : msaGapLength,
|
|
134
131
|
monomerToShort: monomerToShortFunction,
|
|
135
132
|
};
|
|
136
133
|
});
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (
|
|
140
|
-
tableCol.temp[MmcrTemps.rendererSettingsChanged] === rendererSettingsChangedState.true ||
|
|
141
|
-
seqColTemp.monomerLengthLimit != maxLengthOfMonomer
|
|
142
|
-
) {
|
|
143
|
-
gapLength = tableColTemp[MmcrTemps.gapLength] as number ?? gapLength;
|
|
144
|
-
// this event means that the mm renderer settings have changed,
|
|
145
|
-
// particularly monomer representation and max width.
|
|
146
|
-
seqColTemp.setMonomerLengthLimit(maxLengthOfMonomer);
|
|
147
|
-
seqColTemp.setSeparatorWidth(seqColTemp.isMsa() ? msaGapLength : gapLength);
|
|
148
|
-
tableCol.temp[MmcrTemps.rendererSettingsChanged] = rendererSettingsChangedState.false;
|
|
134
|
+
tableCol.temp[MmcrTemps.rendererSettingsChanged] === rendererSettingsChangedState.true;
|
|
149
135
|
}
|
|
150
136
|
|
|
151
137
|
seqColTemp.render(g, x, y, w, h, gridCell, _cellStyle);
|
|
@@ -269,15 +255,13 @@ export function drawMoleculeDifferenceOnCanvas(
|
|
|
269
255
|
|
|
270
256
|
let color1 = undefinedColor;
|
|
271
257
|
if (monomerLib) {
|
|
272
|
-
|
|
273
|
-
color1 = wem1.backgroundcolor!;
|
|
258
|
+
color1 = monomerLib.getMonomerTextColor(biotype, amino1);
|
|
274
259
|
}
|
|
275
260
|
|
|
276
261
|
if (amino1 != amino2) {
|
|
277
262
|
let color2 = undefinedColor;
|
|
278
263
|
if (monomerLib) {
|
|
279
|
-
|
|
280
|
-
color2 = wem2.backgroundcolor!;
|
|
264
|
+
color2 = monomerLib.getMonomerTextColor(biotype, amino2);
|
|
281
265
|
}
|
|
282
266
|
const subX0 = printLeftOrCentered(g, amino1, updatedX, updatedY - vShift, w, h,
|
|
283
267
|
{color: color1, pivot: 0, left: true});
|
|
@@ -41,8 +41,7 @@ export class MonomerCellRendererBack extends CellRendererWithMonomerLibBackBase
|
|
|
41
41
|
if (this.monomerLib) {
|
|
42
42
|
const alphabet = this.tableCol.getTag(bioTAGS.alphabet);
|
|
43
43
|
const biotype = alphabet === ALPHABET.RNA || alphabet === ALPHABET.DNA ? HelmTypes.NUCLEOTIDE : HelmTypes.AA;
|
|
44
|
-
|
|
45
|
-
color = wem.backgroundcolor!;
|
|
44
|
+
color = this.monomerLib.getMonomerTextColor(biotype, symbol);
|
|
46
45
|
}
|
|
47
46
|
|
|
48
47
|
//cell width of monomer should dictate how many characters can be displayed
|
|
@@ -6,15 +6,18 @@ import wu from 'wu';
|
|
|
6
6
|
import {Observable, Subject} from 'rxjs';
|
|
7
7
|
|
|
8
8
|
import {IMonomerLibBase, Monomer, RGroup} from '@datagrok-libraries/bio/src/types/index';
|
|
9
|
-
import {HelmAtom, HelmType, IWebEditorMonomer, MonomerType, PolymerType} from '@datagrok-libraries/bio/src/helm/types';
|
|
9
|
+
import {HelmAtom, HelmType, IMonomerColors, IWebEditorMonomer, MonomerType, PolymerType} from '@datagrok-libraries/bio/src/helm/types';
|
|
10
10
|
import {getMonomerHandleArgs} from '@datagrok-libraries/bio/src/helm/helm-helper';
|
|
11
11
|
import {helmTypeToPolymerType} from '@datagrok-libraries/bio/src/monomer-works/monomer-works';
|
|
12
12
|
import {HelmTypes, PolymerTypes} from '@datagrok-libraries/bio/src/helm/consts';
|
|
13
|
-
import {HELM_REQUIRED_FIELD as REQ, HELM_RGROUP_FIELDS as RGP} from '@datagrok-libraries/bio/src/utils/const';
|
|
13
|
+
import {HELM_OPTIONAL_FIELDS as OPT, HELM_REQUIRED_FIELD as REQ, HELM_RGROUP_FIELDS as RGP} from '@datagrok-libraries/bio/src/utils/const';
|
|
14
14
|
import {GAP_SYMBOL, GapOriginals, NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule/consts';
|
|
15
|
+
import {Vector} from '@datagrok-libraries/utils/src/type-declarations';
|
|
16
|
+
import {vectorAdd, vectorDotProduct, vectorLength} from '@datagrok-libraries/utils/src/vector-operations';
|
|
15
17
|
|
|
16
18
|
import {AmbiguousWebEditorMonomer, GapWebEditorMonomer, MissingWebEditorMonomer} from './web-editor-monomer-dummy';
|
|
17
19
|
import {LibraryWebEditorMonomer} from './web-editor-monomer-of-library';
|
|
20
|
+
import {naturalMonomerColors} from './monomer-colors';
|
|
18
21
|
|
|
19
22
|
import {_package} from '../../package';
|
|
20
23
|
|
|
@@ -24,6 +27,10 @@ const ambMonomerRe = RegExp(String.raw`\(${monomerRe}(,${monomerRe})+\)`);
|
|
|
24
27
|
|
|
25
28
|
export type MonomerLibDataType = { [polymerType: string]: { [monomerSymbol: string]: Monomer } };
|
|
26
29
|
|
|
30
|
+
const whiteColorV = new Vector([255.0, 255.0, 255.0]);
|
|
31
|
+
const blackColorV = new Vector([0.0, 0.0, 0.0]);
|
|
32
|
+
const maxTextColorVLen = vectorLength(whiteColorV) * 0.7;
|
|
33
|
+
|
|
27
34
|
export class MonomerLibBase implements IMonomerLibBase {
|
|
28
35
|
protected _isEmpty: boolean;
|
|
29
36
|
get isEmpty(): boolean { return this._isEmpty; }
|
|
@@ -143,7 +150,7 @@ export class MonomerLibBase implements IMonomerLibBase {
|
|
|
143
150
|
resWem = m.wem = LibraryWebEditorMonomer.fromMonomer(biotype, m, this);
|
|
144
151
|
}
|
|
145
152
|
|
|
146
|
-
return resWem
|
|
153
|
+
return resWem;
|
|
147
154
|
}
|
|
148
155
|
|
|
149
156
|
getTooltip(biotype: HelmType, monomerSymbol: string): HTMLElement {
|
|
@@ -155,10 +162,18 @@ export class MonomerLibBase implements IMonomerLibBase {
|
|
|
155
162
|
const symbol = monomer[REQ.SYMBOL];
|
|
156
163
|
const _name = monomer[REQ.NAME];
|
|
157
164
|
const wem = this.getWebEditorMonomer(biotype, monomerSymbol)!;
|
|
165
|
+
|
|
158
166
|
const htmlColor = wem.backgroundcolor;
|
|
159
167
|
res.append(ui.divH([
|
|
160
|
-
ui.div([symbol], {
|
|
161
|
-
|
|
168
|
+
ui.div([symbol], {
|
|
169
|
+
style: {
|
|
170
|
+
/* fontWeight: 'bolder', */ textWrap: 'nowrap', marginLeft: '4px', marginRight: '4px',
|
|
171
|
+
color: wem.textcolor, backgroundColor: wem.backgroundcolor, borderColor: wem.linecolor,
|
|
172
|
+
borderWidth: '1px', borderStyle: 'solid', borderRadius: '2px', padding: '3px',
|
|
173
|
+
minWidth: '24px', textAlign: 'center',
|
|
174
|
+
}
|
|
175
|
+
}),
|
|
176
|
+
ui.div([monomer.name], {style: {padding: '4px'}}),
|
|
162
177
|
], {style: {display: 'flex', flexDirection: 'row', justifyContent: 'left'}}));
|
|
163
178
|
|
|
164
179
|
// Structure
|
|
@@ -190,6 +205,77 @@ export class MonomerLibBase implements IMonomerLibBase {
|
|
|
190
205
|
return res;
|
|
191
206
|
}
|
|
192
207
|
|
|
208
|
+
getMonomerTextColor(biotype: HelmType, symbol: string): string {
|
|
209
|
+
const colors: IMonomerColors = this.getMonomerColors(biotype, symbol);
|
|
210
|
+
const htmlToA = (html: string): number[] => {
|
|
211
|
+
const rgbM = /rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/.exec(html);
|
|
212
|
+
if (rgbM)
|
|
213
|
+
return [parseInt(rgbM[1]), parseInt(rgbM[2]), parseInt(rgbM[3])];
|
|
214
|
+
|
|
215
|
+
const n = DG.Color.fromHtml(colors.textcolor!);
|
|
216
|
+
return [DG.Color.r(n), DG.Color.g(n), DG.Color.b(n)];
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const textColorA = htmlToA(colors.textcolor!);
|
|
220
|
+
const textColorAdjV = new Vector([...textColorA.map((v) => v + 1)]);
|
|
221
|
+
const cosTextToWhite = vectorDotProduct(textColorAdjV, whiteColorV) /
|
|
222
|
+
(vectorLength(textColorAdjV) * vectorLength(whiteColorV));
|
|
223
|
+
|
|
224
|
+
const backColorA = htmlToA(colors.backgroundcolor!);
|
|
225
|
+
const backColorAdjV = new Vector([...backColorA.map((v) => v + 0.01)]);
|
|
226
|
+
const cosBackToWhite = vectorDotProduct(backColorAdjV, whiteColorV) /
|
|
227
|
+
(vectorLength(backColorAdjV) * vectorLength(whiteColorV));
|
|
228
|
+
|
|
229
|
+
let resColorA;
|
|
230
|
+
if (cosBackToWhite < cosTextToWhite)
|
|
231
|
+
resColorA = backColorA;
|
|
232
|
+
else
|
|
233
|
+
resColorA = textColorA;
|
|
234
|
+
|
|
235
|
+
let resColorV = new Vector(resColorA);
|
|
236
|
+
const resColorLen = vectorLength(resColorV);
|
|
237
|
+
if (resColorLen > maxTextColorVLen) resColorV = vectorAdd(blackColorV, resColorV, maxTextColorVLen / resColorLen);
|
|
238
|
+
return `rgb(${resColorV[0]}, ${resColorV[1]}, ${resColorV[2]})`;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
getMonomerColors(biotype: HelmType, symbol: string): IMonomerColors {
|
|
242
|
+
const currentMonomerSchema = 'default';
|
|
243
|
+
let monomerSchema: string = currentMonomerSchema;
|
|
244
|
+
|
|
245
|
+
const polymerType = helmTypeToPolymerType(biotype);
|
|
246
|
+
const monomer = this.getMonomer(polymerType, symbol)!;
|
|
247
|
+
let res: any;
|
|
248
|
+
if (monomer) {
|
|
249
|
+
if (monomer.meta && monomer.meta.colors) {
|
|
250
|
+
const monomerColors: { [colorSchemaName: string]: any } = monomer.meta.colors;
|
|
251
|
+
if (!(currentMonomerSchema in monomerColors)) monomerSchema = 'default';
|
|
252
|
+
res = monomerColors[monomerSchema];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (!res) {
|
|
256
|
+
const biotypeColors: { [symbol: string]: string } | undefined = naturalMonomerColors[biotype];
|
|
257
|
+
const nColor: string = biotypeColors?.[monomer.symbol];
|
|
258
|
+
if (nColor) {
|
|
259
|
+
const nTextColor = DG.Color.toHtml(DG.Color.getContrastColor(DG.Color.fromHtml(nColor)));
|
|
260
|
+
res = {textColor: nTextColor, lineColor: '#202020', backgroundColor: nColor};
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const naSymbol: string | undefined = monomer[OPT.NATURAL_ANALOG];
|
|
265
|
+
if (!res && naSymbol) {
|
|
266
|
+
return this.getMonomerColors(biotype, naSymbol);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (!res)
|
|
271
|
+
res = {textColor: "#202020", lineColor: "#202020", backgroundColor: "#A0A0A0"};
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
textcolor: res.text ?? res.textColor,
|
|
275
|
+
linecolor: res.line ?? res.lineColor,
|
|
276
|
+
backgroundcolor: res.background ?? res.backgroundColor
|
|
277
|
+
} as IMonomerColors;
|
|
278
|
+
}
|
|
193
279
|
|
|
194
280
|
getRS(smiles: string): { [r: string]: string } {
|
|
195
281
|
const newS = smiles.match(/(?<=\[)[^\][]*(?=])/gm);
|
|
@@ -16,6 +16,7 @@ import {MonomerLibManager} from '../lib-manager';
|
|
|
16
16
|
import {LIB_PATH} from '../consts';
|
|
17
17
|
|
|
18
18
|
import '../../../../css/monomer-manager.css';
|
|
19
|
+
import { MONOMER_RENDERER_TAGS } from '@datagrok-libraries/bio/src/utils/cell-renderer';
|
|
19
20
|
|
|
20
21
|
// columns of monomers dataframe, note that rgroups is hidden and will be displayed as separate columns
|
|
21
22
|
export enum MONOMER_DF_COLUMN_NAMES {
|
|
@@ -50,6 +51,16 @@ export const MONOMER_DF_COLUMNS = {
|
|
|
50
51
|
|
|
51
52
|
|
|
52
53
|
export class MonomerManager implements IMonomerManager {
|
|
54
|
+
|
|
55
|
+
private adjustColWidths() {
|
|
56
|
+
setTimeout(() => {
|
|
57
|
+
if (this.tv?.grid) {
|
|
58
|
+
this.tv!.grid.col(MONOMER_DF_COLUMN_NAMES.NAME)!.width = 100;
|
|
59
|
+
this.tv!.grid.col(MONOMER_DF_COLUMN_NAMES.SYMBOL)!.width = 70;
|
|
60
|
+
}
|
|
61
|
+
}, 200);
|
|
62
|
+
}
|
|
63
|
+
|
|
53
64
|
public static readonly VIEW_NAME = 'Manage Monomers';
|
|
54
65
|
private _newMonomer: Monomer = DUMMY_MONOMER;
|
|
55
66
|
private _newMonomerForm: MonomerForm;
|
|
@@ -63,7 +74,11 @@ export class MonomerManager implements IMonomerManager {
|
|
|
63
74
|
this.monomerLib = monomerLibManamger.getBioLib();
|
|
64
75
|
this._newMonomerForm = new MonomerForm(monomerLibManamger, () => this.activeMonomerLib, async () => {
|
|
65
76
|
const df = await this.getMonomersDf(this.libInput.value!);
|
|
66
|
-
|
|
77
|
+
if (this.tv?.dataFrame) {
|
|
78
|
+
this.tv.dataFrame = df;
|
|
79
|
+
this.adjustColWidths();
|
|
80
|
+
}
|
|
81
|
+
|
|
67
82
|
}, () => this.tv?.dataFrame);
|
|
68
83
|
}
|
|
69
84
|
|
|
@@ -150,7 +165,7 @@ export class MonomerManager implements IMonomerManager {
|
|
|
150
165
|
const df = await this.getMonomersDf(fileName);
|
|
151
166
|
this.tv = DG.TableView.create(df, true);
|
|
152
167
|
//const f = tv.filters();
|
|
153
|
-
this.
|
|
168
|
+
this.adjustColWidths();
|
|
154
169
|
this.tv.subs.push(
|
|
155
170
|
grok.events.onContextMenu.subscribe(({args}) => {
|
|
156
171
|
if (!args || !args.menu || !args.context || args.context.type !== DG.VIEWER.GRID || !args.context.tableView ||
|
|
@@ -200,7 +215,7 @@ export class MonomerManager implements IMonomerManager {
|
|
|
200
215
|
libName && (this.libInput.value = libName);
|
|
201
216
|
const df = await this.getMonomersDf(libName);
|
|
202
217
|
this.tv.dataFrame = df;
|
|
203
|
-
this.
|
|
218
|
+
this.adjustColWidths();
|
|
204
219
|
return this.tv;
|
|
205
220
|
}
|
|
206
221
|
|
|
@@ -262,6 +277,7 @@ export class MonomerManager implements IMonomerManager {
|
|
|
262
277
|
try {
|
|
263
278
|
const df = await this.getMonomersDf(this.libInput.value!);
|
|
264
279
|
this.tv!.dataFrame = df;
|
|
280
|
+
this.adjustColWidths();
|
|
265
281
|
} catch (e) {
|
|
266
282
|
console.error(e);
|
|
267
283
|
}
|
|
@@ -311,6 +327,8 @@ export class MonomerManager implements IMonomerManager {
|
|
|
311
327
|
df.columns.addNew(rgroupName, DG.COLUMN_TYPE.STRING);
|
|
312
328
|
}
|
|
313
329
|
}
|
|
330
|
+
df.col(MONOMER_DF_COLUMN_NAMES.SYMBOL)!.semType = 'Monomer';
|
|
331
|
+
df.col(MONOMER_DF_COLUMN_NAMES.SYMBOL)!.setTag(MONOMER_RENDERER_TAGS.applyToBackground, 'true');
|
|
314
332
|
|
|
315
333
|
|
|
316
334
|
for (let i = 0; i < monomers.length; i++) {
|
|
@@ -470,16 +488,29 @@ class MonomerForm implements INewMonomerForm {
|
|
|
470
488
|
monomerNaturalAnalogInput: DG.InputBase<string | null>;
|
|
471
489
|
rgroupsGrid: ItemsGrid;
|
|
472
490
|
metaGrid: ItemsGrid;
|
|
491
|
+
colors: {
|
|
492
|
+
line: string,
|
|
493
|
+
background: string,
|
|
494
|
+
text: string
|
|
495
|
+
};
|
|
496
|
+
colorsEditor: ColorsEditor;
|
|
473
497
|
saveButton: HTMLButtonElement;
|
|
474
498
|
rgroupsGridRoot: HTMLElement;
|
|
475
499
|
private _molChanged: boolean = false;
|
|
476
500
|
get molChanged() { return this._molChanged; }
|
|
477
501
|
private saveValidationResult?: string | null = null;
|
|
478
502
|
private triggerMolChange: boolean = true; // makes sure that change is not triggered by copying the molecule from grid
|
|
503
|
+
inputsTabControl: DG.TabControl;
|
|
479
504
|
constructor(public monomerLibManager: MonomerLibManager,
|
|
480
505
|
private getMonomerLib: () => IMonomerLib | null, private refreshTable: () => Promise<void>,
|
|
481
506
|
private getMonomersDataFrame: () => DG.DataFrame | undefined) {
|
|
482
507
|
const monomerTypes = ['PEPTIDE', 'RNA', 'CHEM', 'BLOB', 'G'];
|
|
508
|
+
this.colors = {
|
|
509
|
+
line: '#000000',
|
|
510
|
+
background: '#000000',
|
|
511
|
+
text: '#000000',
|
|
512
|
+
};
|
|
513
|
+
this.colorsEditor = new ColorsEditor(this.colors);
|
|
483
514
|
this.molSketcher = new DG.chem.Sketcher();
|
|
484
515
|
this.molSketcher.root.classList.add('monomer-manager-sketcher');
|
|
485
516
|
this.polymerTypeInput = ui.input.choice('Polymer Type', {value: 'PEPTIDE', items: monomerTypes,
|
|
@@ -576,6 +607,24 @@ class MonomerForm implements INewMonomerForm {
|
|
|
576
607
|
];
|
|
577
608
|
this.metaGrid = new ItemsGrid(metaProps, []);
|
|
578
609
|
this.onMonomerInputChanged();
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
const mainInputsDiv = ui.divV([
|
|
613
|
+
this.polymerTypeInput,
|
|
614
|
+
this.monomerTypeInput,
|
|
615
|
+
this.monomerSymbolInput,
|
|
616
|
+
this.monomerNameInput,
|
|
617
|
+
this.monomerIdInput,
|
|
618
|
+
this.monomerNaturalAnalogInput,
|
|
619
|
+
]);
|
|
620
|
+
|
|
621
|
+
this.inputsTabControl = ui.tabControl({
|
|
622
|
+
'Monomer': mainInputsDiv,
|
|
623
|
+
'R-groups': this.rgroupsGridRoot,
|
|
624
|
+
'Meta': ui.divV([this.metaGrid.root]),
|
|
625
|
+
'Colors': this.colorsEditor.form,
|
|
626
|
+
}, false);
|
|
627
|
+
|
|
579
628
|
}
|
|
580
629
|
|
|
581
630
|
onMonomerInputChanged() {
|
|
@@ -603,13 +652,12 @@ class MonomerForm implements INewMonomerForm {
|
|
|
603
652
|
this.monomerIdInput.value = monomer.id;
|
|
604
653
|
this.monomerNaturalAnalogInput.value = monomer.naturalAnalog ?? null;
|
|
605
654
|
this.rgroupsGrid.items = resolveRGroupInfo(monomer.rgroups);
|
|
606
|
-
this.metaGrid.items = Object.entries(monomer.meta ?? {}).map(([k, v]) => {
|
|
655
|
+
this.metaGrid.items = Object.entries(monomer.meta ?? {}).filter(([k, v]) => k?.toLowerCase() !== 'colors').map(([k, v]) => {
|
|
607
656
|
return {Property: k, Value: v};
|
|
608
657
|
});
|
|
609
658
|
this.rgroupsGrid.render();
|
|
610
659
|
this.metaGrid.render();
|
|
611
660
|
this.rgroupsGridRoot.style.display = 'flex';
|
|
612
|
-
|
|
613
661
|
this.onMonomerInputChanged();
|
|
614
662
|
if (!monomer.naturalAnalog) {
|
|
615
663
|
mostSimilarNaturalAnalog(capSmiles(monomer.smiles, this.rgroupsGrid.items as RGroup[]), monomer.polymerType).then((mostSimilar) => {
|
|
@@ -617,20 +665,50 @@ class MonomerForm implements INewMonomerForm {
|
|
|
617
665
|
this.monomerNaturalAnalogInput.value = mostSimilar;
|
|
618
666
|
});
|
|
619
667
|
}
|
|
668
|
+
const colorsString = monomer.meta?.colors ?? '';
|
|
669
|
+
let colorsObj: Partial<typeof this.colors> = {};
|
|
670
|
+
try {
|
|
671
|
+
colorsObj = colorsString ? JSON.parse(colorsString)?.default : {};
|
|
672
|
+
} catch (e) {
|
|
673
|
+
console.error(e);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
this.colorsEditor.colors = {
|
|
677
|
+
line: colorsObj.line ?? '#000000',
|
|
678
|
+
background: colorsObj.background ?? '#000000',
|
|
679
|
+
text: colorsObj.text ?? '#000000',
|
|
680
|
+
};
|
|
620
681
|
}
|
|
621
682
|
|
|
622
683
|
validateInputs(): string | null | undefined {
|
|
684
|
+
const rGroupsPane = this.inputsTabControl.panes.find((p) => p.name?.toLowerCase() === 'r-groups');
|
|
685
|
+
rGroupsPane && (rGroupsPane.header.style.removeProperty('background-color'));
|
|
623
686
|
if (!this.molSketcher.getSmiles()) return 'Monomer Molecule field is required';
|
|
624
|
-
for (const i of [this.polymerTypeInput, this.monomerTypeInput, this.monomerSymbolInput, this.monomerNameInput, this.monomerIdInput
|
|
687
|
+
for (const i of [this.polymerTypeInput, this.monomerTypeInput, this.monomerSymbolInput, this.monomerNameInput, this.monomerIdInput]) {
|
|
625
688
|
if (i.value == null || i.value === '')
|
|
626
689
|
return `${i.caption} field is required`;
|
|
627
690
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
691
|
+
let rgroupError: string | null | undefined = null;
|
|
692
|
+
if (this.rgroupsGrid.items.length < 1)
|
|
693
|
+
rgroupError = 'At least one R-group is required';
|
|
694
|
+
if (!rgroupError) {
|
|
695
|
+
outerFor:
|
|
696
|
+
for (const item of this.rgroupsGrid.items) {
|
|
697
|
+
for (const [k, v] of Object.entries(item))
|
|
698
|
+
if (!v){
|
|
699
|
+
rgroupError = `R-group ${k} field is required for ${item[HELM_RGROUP_FIELDS.LABEL]}`;
|
|
700
|
+
break outerFor;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
if (!rgroupError && this.rgroupsGrid.hasErrors()){
|
|
706
|
+
rgroupError = 'R-group fields contain errors';
|
|
707
|
+
}
|
|
708
|
+
if (rgroupError) {
|
|
709
|
+
rGroupsPane && (rGroupsPane.header.style.setProperty('background-color', '#ff000030'));
|
|
710
|
+
return rgroupError;
|
|
632
711
|
}
|
|
633
|
-
if (this.rgroupsGrid.hasErrors()) return 'R-group fields contain errors';
|
|
634
712
|
return null;
|
|
635
713
|
}
|
|
636
714
|
|
|
@@ -643,26 +721,14 @@ class MonomerForm implements INewMonomerForm {
|
|
|
643
721
|
}
|
|
644
722
|
|
|
645
723
|
get form() {
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
this.monomerSymbolInput,
|
|
650
|
-
this.monomerNameInput,
|
|
651
|
-
this.monomerIdInput,
|
|
652
|
-
this.monomerNaturalAnalogInput,
|
|
653
|
-
]);
|
|
654
|
-
|
|
655
|
-
const inputsPanel = ui.tabControl({
|
|
656
|
-
'Monomer': mainInputsDiv,
|
|
657
|
-
'R-groups': this.rgroupsGridRoot,
|
|
658
|
-
'Meta': ui.divV([this.metaGrid.root]),
|
|
659
|
-
}, false);
|
|
660
|
-
inputsPanel.header.style.marginBottom = '10px';
|
|
724
|
+
|
|
725
|
+
this.inputsTabControl.root.classList.add('monomer-manager-form-tab-control');
|
|
726
|
+
this.inputsTabControl.header.style.marginBottom = '10px';
|
|
661
727
|
const saveB = ui.buttonsInput([this.saveButton]);
|
|
662
728
|
ui.tooltip.bind(saveB, () => this.saveValidationResult ?? 'Save monomer to library');
|
|
663
729
|
return ui.divV([
|
|
664
730
|
this.molSketcher.root,
|
|
665
|
-
|
|
731
|
+
this.inputsTabControl.root,
|
|
666
732
|
saveB,
|
|
667
733
|
], {classes: 'ui-form', style: {paddingLeft: '10px', overflow: 'scroll'}});
|
|
668
734
|
}
|
|
@@ -750,8 +816,12 @@ class MonomerForm implements INewMonomerForm {
|
|
|
750
816
|
const saveLib = async () => {
|
|
751
817
|
try {
|
|
752
818
|
// first remove the existing monomer with that symbol
|
|
753
|
-
|
|
754
|
-
|
|
819
|
+
const monomerIdx = libJSON.findIndex((m) => m.symbol === monomer.symbol && m.polymerType === monomer.polymerType);
|
|
820
|
+
if (monomerIdx >= 0) {
|
|
821
|
+
libJSON[monomerIdx] = {...monomer, lib: undefined, wem: undefined};
|
|
822
|
+
} else {
|
|
823
|
+
libJSON.push({...monomer, lib: undefined, wem: undefined});
|
|
824
|
+
}
|
|
755
825
|
await grok.dapi.files.writeAsText(LIB_PATH + libName, JSON.stringify(libJSON));
|
|
756
826
|
await (await MonomerLibManager.getInstance()).loadLibraries(true);
|
|
757
827
|
await this.refreshTable();
|
|
@@ -802,6 +872,13 @@ class MonomerForm implements INewMonomerForm {
|
|
|
802
872
|
this.metaGrid.items.filter((item) => (!!item['Property']) && (!!item['Value'])).forEach((item) => {
|
|
803
873
|
meta[item['Property']] = item['Value'];
|
|
804
874
|
});
|
|
875
|
+
const addingItem = this.metaGrid.addingItem;
|
|
876
|
+
if (addingItem && addingItem['Property'] && addingItem['Value']) {
|
|
877
|
+
meta[addingItem['Property']] = addingItem['Value'];
|
|
878
|
+
}
|
|
879
|
+
//console.log(this.metaGrid.addingItem);
|
|
880
|
+
if (this.colorsEditor.colors.line !== '#000000' || this.colorsEditor.colors.background !== '#000000' || this.colorsEditor.colors.text !== '#000000')
|
|
881
|
+
meta.colors = {default: this.colorsEditor.colors};
|
|
805
882
|
const monomer: Monomer = {
|
|
806
883
|
symbol: this.monomerSymbolInput.value,
|
|
807
884
|
name: this.monomerNameInput.value,
|
|
@@ -929,3 +1006,42 @@ function monomerFromDfRow(dfRow: DG.Row): Monomer {
|
|
|
929
1006
|
createDate: dfRow.get(MONOMER_DF_COLUMN_NAMES.CREATE_DATE),
|
|
930
1007
|
};
|
|
931
1008
|
}
|
|
1009
|
+
|
|
1010
|
+
class ColorsEditor {
|
|
1011
|
+
private _colors: { line: string, background: string, text: string };
|
|
1012
|
+
private _colorInputs: { [key in keyof ColorsEditor['_colors']]: DG.InputBase<string> };
|
|
1013
|
+
constructor(colors: { line: string, background: string, text: string }) {
|
|
1014
|
+
this._colors = colors;
|
|
1015
|
+
this._colorInputs = {
|
|
1016
|
+
line: ui.input.color('Line', {value: colors.line, onValueChanged: (v) => this._colors.line = v}),
|
|
1017
|
+
background: ui.input.color('Background', {value: colors.background, onValueChanged: (v) => this._colors.background = v}),
|
|
1018
|
+
text: ui.input.color('Text', {value: colors.text, onValueChanged: (v) => this._colors.text = v}),
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
get colors() {
|
|
1023
|
+
return this._colors;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
set colors(cols: { line: string, background: string, text: string }) {
|
|
1027
|
+
//need to convert to hex as the input accepts only hex
|
|
1028
|
+
const colsHex = {
|
|
1029
|
+
line: DG.Color.toHtml(DG.Color.fromHtml(cols.line ?? '#000000')),
|
|
1030
|
+
background: DG.Color.toHtml(DG.Color.fromHtml(cols.background ?? '#000000')),
|
|
1031
|
+
text: DG.Color.toHtml(DG.Color.fromHtml(cols.text ?? '#000000')),
|
|
1032
|
+
};
|
|
1033
|
+
|
|
1034
|
+
this._colors = colsHex;
|
|
1035
|
+
for (const key in this._colorInputs) {
|
|
1036
|
+
this._colorInputs[key as keyof ColorsEditor['_colors']].value = colsHex[key as keyof ColorsEditor['_colors']];
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
get colorsMetaFormat() {
|
|
1041
|
+
return {colors: {default: this._colors}};
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
get form() {
|
|
1045
|
+
return ui.form(Object.values(this._colorInputs));
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
@@ -2,12 +2,11 @@ import * as grok from 'datagrok-api/grok';
|
|
|
2
2
|
import * as ui from 'datagrok-api/ui';
|
|
3
3
|
import * as DG from 'datagrok-api/dg';
|
|
4
4
|
|
|
5
|
-
import {HelmType,
|
|
5
|
+
import {HelmType, IWebEditorMonomer, MonomerType, PolymerType, WebEditorRGroups} from '@datagrok-libraries/bio/src/helm/types';
|
|
6
6
|
import {IMonomerLibBase, Monomer} from '@datagrok-libraries/bio/src/types/index';
|
|
7
7
|
import {HELM_OPTIONAL_FIELDS as OPT, HELM_REQUIRED_FIELD as REQ, HELM_RGROUP_FIELDS as RGP} from '@datagrok-libraries/bio/src/utils/const';
|
|
8
8
|
|
|
9
|
-
import {BrokenWebEditorMonomer
|
|
10
|
-
import {naturalMonomerColors} from './monomer-colors';
|
|
9
|
+
import {BrokenWebEditorMonomer} from './web-editor-monomer-dummy';
|
|
11
10
|
|
|
12
11
|
export class LibraryWebEditorMonomer implements IWebEditorMonomer {
|
|
13
12
|
public get rs(): number { return Object.keys(this.at).length; }
|
|
@@ -59,7 +58,7 @@ export class LibraryWebEditorMonomer implements IWebEditorMonomer {
|
|
|
59
58
|
monomer[REQ.MONOMER_TYPE],
|
|
60
59
|
at);
|
|
61
60
|
|
|
62
|
-
const colors = getMonomerColors(biotype, monomer
|
|
61
|
+
const colors = monomerLib.getMonomerColors(biotype, monomer[REQ.SYMBOL]);
|
|
63
62
|
if (colors) {
|
|
64
63
|
res.textcolor = colors?.textcolor;
|
|
65
64
|
res.linecolor = colors?.linecolor;
|
|
@@ -69,41 +68,3 @@ export class LibraryWebEditorMonomer implements IWebEditorMonomer {
|
|
|
69
68
|
return res;
|
|
70
69
|
}
|
|
71
70
|
}
|
|
72
|
-
|
|
73
|
-
function getMonomerColors(biotype: HelmType, monomer: Monomer, monomerLib?: IMonomerLibBase): IMonomerColors | null {
|
|
74
|
-
const currentMonomerSchema = 'default';
|
|
75
|
-
let monomerSchema: string = currentMonomerSchema;
|
|
76
|
-
|
|
77
|
-
let res: any;
|
|
78
|
-
if (monomer.meta && monomer.meta.colors) {
|
|
79
|
-
const monomerColors: { [colorSchemaName: string]: any } = monomer.meta.colors;
|
|
80
|
-
if (!(currentMonomerSchema in monomerColors)) monomerSchema = 'default';
|
|
81
|
-
res = monomerColors[monomerSchema];
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (!res) {
|
|
85
|
-
const biotypeColors: { [symbol: string]: string } | undefined = naturalMonomerColors[biotype];
|
|
86
|
-
const nColor: string = biotypeColors?.[monomer.symbol];
|
|
87
|
-
if (nColor) {
|
|
88
|
-
const nTextColor = DG.Color.toHtml(DG.Color.getContrastColor(DG.Color.fromHtml(nColor)));
|
|
89
|
-
res = {textColor: nTextColor, lineColor: '#202020', backgroundColor: nColor};
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const naSymbol: string | undefined = monomer[OPT.NATURAL_ANALOG];
|
|
94
|
-
if (!res && naSymbol) {
|
|
95
|
-
const polymerType = monomer[REQ.POLYMER_TYPE];
|
|
96
|
-
const naMonomer = monomerLib?.getMonomer(polymerType, naSymbol);
|
|
97
|
-
if (naMonomer)
|
|
98
|
-
return getMonomerColors(biotype, naMonomer);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (!res)
|
|
102
|
-
res = {textColor: "#202020", lineColor: "#202020", backgroundColor: "#A0A0A0"};
|
|
103
|
-
|
|
104
|
-
return !res ? null : {
|
|
105
|
-
textcolor: res.text ?? res.textColor,
|
|
106
|
-
linecolor: res.line ?? res.lineColor,
|
|
107
|
-
backgroundcolor: res.background ?? res.backgroundColor
|
|
108
|
-
} as IMonomerColors;
|
|
109
|
-
}
|