@datagrok/bio 2.6.1 → 2.7.1
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/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 +7 -1
- package/src/package-types.ts +10 -0
- package/src/package.ts +18 -48
- package/src/tests/monomer-libraries-tests.ts +14 -1
- package/src/utils/macromolecule-column-widget.ts +47 -0
- package/src/utils/monomer-lib.ts +61 -12
- package/src/viewers/vd-regions-viewer.ts +55 -15
- package/src/viewers/web-logo-viewer.ts +14 -11
- package/src/widgets/package-settings-editor-widget.ts +11 -1
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"name": "Leonid Stolbov",
|
|
6
6
|
"email": "lstolbov@datagrok.ai"
|
|
7
7
|
},
|
|
8
|
-
"version": "2.
|
|
8
|
+
"version": "2.7.1",
|
|
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",
|
|
@@ -18,6 +18,12 @@
|
|
|
18
18
|
"propertyType": "int",
|
|
19
19
|
"defaultValue": 3,
|
|
20
20
|
"nullable": false
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "TooltipWebLogo",
|
|
24
|
+
"propertyType": "bool",
|
|
25
|
+
"defaultValue": "true",
|
|
26
|
+
"nullable": false
|
|
21
27
|
}
|
|
22
28
|
],
|
|
23
29
|
"dependencies": {
|
package/src/package-types.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {ObjectPropertyBag} from 'datagrok-api/dg';
|
|
|
8
8
|
/** Names of package properties/settings declared in properties section of {@link './package.json'} */
|
|
9
9
|
export const enum BioPackagePropertiesNames {
|
|
10
10
|
MaxMonomerLength = 'MaxMonomerLength',
|
|
11
|
+
TooltipWebLogo = 'TooltipWebLogo',
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
|
|
@@ -26,6 +27,15 @@ export class BioPackageProperties extends Map<string, any> {
|
|
|
26
27
|
this._onPropertyChanged.next(BioPackagePropertiesNames.MaxMonomerLength);
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
public get tooltipWebLogo(): boolean {
|
|
31
|
+
return super.get(BioPackagePropertiesNames.TooltipWebLogo) as unknown as boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public set tooltipWebLogo(value: boolean) {
|
|
35
|
+
super.set(BioPackagePropertiesNames.TooltipWebLogo, value as unknown as boolean);
|
|
36
|
+
this._onPropertyChanged.next(BioPackagePropertiesNames.TooltipWebLogo);
|
|
37
|
+
}
|
|
38
|
+
|
|
29
39
|
constructor(source: any) {
|
|
30
40
|
super(Object.entries(source));
|
|
31
41
|
}
|
package/src/package.ts
CHANGED
|
@@ -33,7 +33,13 @@ import {SeqPalette} from '@datagrok-libraries/bio/src/seq-palettes';
|
|
|
33
33
|
import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
|
|
34
34
|
import {WebLogoViewer} from './viewers/web-logo-viewer';
|
|
35
35
|
import {createJsonMonomerLibFromSdf, IMonomerLibHelper} from '@datagrok-libraries/bio/src/monomer-works/monomer-utils';
|
|
36
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
MonomerLibHelper,
|
|
38
|
+
getUserLibSettings,
|
|
39
|
+
setUserLibSetting,
|
|
40
|
+
getLibFileNameList,
|
|
41
|
+
getLibraryPanelUI
|
|
42
|
+
} from './utils/monomer-lib';
|
|
37
43
|
import {getMacromoleculeColumn} from './utils/ui-utils';
|
|
38
44
|
import {DimReductionMethods, ITSNEOptions, IUMAPOptions} from '@datagrok-libraries/ml/src/reduce-dimensionality';
|
|
39
45
|
import {SequenceSpaceFunctionEditor} from '@datagrok-libraries/ml/src/functionEditors/seq-space-editor';
|
|
@@ -55,9 +61,9 @@ import {splitToMonomersUI} from './utils/split-to-monomers';
|
|
|
55
61
|
import {MonomerCellRenderer} from './utils/monomer-cell-renderer';
|
|
56
62
|
import {BioPackage, BioPackageProperties} from './package-types';
|
|
57
63
|
import {RDModule} from '@datagrok-libraries/chem-meta/src/rdkit-api';
|
|
58
|
-
import {ObjectPropertyBag} from 'datagrok-api/dg';
|
|
59
64
|
import {PackageSettingsEditorWidget} from './widgets/package-settings-editor-widget';
|
|
60
65
|
import {getCompositionAnalysisWidget} from './widgets/composition-analysis-widget';
|
|
66
|
+
import {MacromoleculeColumnWidget} from './utils/macromolecule-column-widget';
|
|
61
67
|
|
|
62
68
|
export const _package = new BioPackage();
|
|
63
69
|
|
|
@@ -126,17 +132,14 @@ export async function initBio() {
|
|
|
126
132
|
//tags: tooltip
|
|
127
133
|
//input: column col {semType: Macromolecule}
|
|
128
134
|
//output: widget result
|
|
129
|
-
export
|
|
130
|
-
const
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
});
|
|
138
|
-
viewer.root.style.height = '50px';
|
|
139
|
-
return viewer;
|
|
135
|
+
export function sequenceTooltip(col: DG.Column): DG.Widget<any> {
|
|
136
|
+
const resWidget = new MacromoleculeColumnWidget(col);
|
|
137
|
+
const _resPromise = resWidget.init().then(() => { })
|
|
138
|
+
.catch((err: any) => {
|
|
139
|
+
const errMsg = err instanceof Error ? err.message : err.toString();
|
|
140
|
+
grok.shell.error(errMsg);
|
|
141
|
+
});
|
|
142
|
+
return resWidget;
|
|
140
143
|
}
|
|
141
144
|
|
|
142
145
|
//name: getBioLib
|
|
@@ -145,46 +148,12 @@ export function getBioLib(): IMonomerLib {
|
|
|
145
148
|
return MonomerLibHelper.instance.getBioLib();
|
|
146
149
|
}
|
|
147
150
|
|
|
148
|
-
//name: manageFiles
|
|
149
|
-
export async function manageFiles() {
|
|
150
|
-
const a = ui.dialog({title: 'Manage files'})
|
|
151
|
-
//@ts-ignore
|
|
152
|
-
.add(ui.fileBrowser({path: 'System:AppData/Bio/libraries'}).root)
|
|
153
|
-
.addButton('OK', () => a.close())
|
|
154
|
-
.show();
|
|
155
|
-
}
|
|
156
|
-
|
|
157
151
|
//name: Manage Libraries
|
|
158
152
|
//input: column seqColumn {semType: Macromolecule}
|
|
159
153
|
//tags: panel, exclude-actions-panel
|
|
160
154
|
//output: widget result
|
|
161
155
|
export async function libraryPanel(_seqColumn: DG.Column): Promise<DG.Widget> {
|
|
162
|
-
|
|
163
|
-
const filesButton: HTMLButtonElement = ui.button('Manage', manageFiles);
|
|
164
|
-
const inputsForm: HTMLDivElement = ui.inputs([]);
|
|
165
|
-
const libFileNameList: string[] = await getLibFileNameList();
|
|
166
|
-
|
|
167
|
-
let userStoragePromise: Promise<void> = Promise.resolve();
|
|
168
|
-
for (const libFileName of libFileNameList) {
|
|
169
|
-
const settings = await getUserLibSettings();
|
|
170
|
-
const libInput: DG.InputBase<boolean | null> = ui.boolInput(libFileName, !settings.exclude.includes(libFileName),
|
|
171
|
-
() => {
|
|
172
|
-
userStoragePromise = userStoragePromise.then(async () => {
|
|
173
|
-
if (libInput.value == true) {
|
|
174
|
-
// Checked library remove from excluded list
|
|
175
|
-
settings.exclude = settings.exclude.filter((l) => l != libFileName);
|
|
176
|
-
} else {
|
|
177
|
-
// Unchecked library add to excluded list
|
|
178
|
-
if (!settings.exclude.includes(libFileName)) settings.exclude.push(libFileName);
|
|
179
|
-
}
|
|
180
|
-
await setUserLibSetting(settings);
|
|
181
|
-
await MonomerLibHelper.instance.loadLibraries(true); // from libraryPanel()
|
|
182
|
-
grok.shell.info('Monomer library user settings saved.');
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
inputsForm.append(libInput.root);
|
|
186
|
-
}
|
|
187
|
-
return new DG.Widget(ui.divV([inputsForm, ui.div(filesButton)]));
|
|
156
|
+
return getLibraryPanelUI();
|
|
188
157
|
}
|
|
189
158
|
|
|
190
159
|
// -- Package settings editor --
|
|
@@ -614,6 +583,7 @@ export function importFasta(fileContent: string): DG.DataFrame [] {
|
|
|
614
583
|
const ffh = new FastaFileHandler(fileContent);
|
|
615
584
|
return ffh.importFasta();
|
|
616
585
|
}
|
|
586
|
+
|
|
617
587
|
//name: importBam
|
|
618
588
|
//description: Opens Bam file
|
|
619
589
|
//tags: file-handler
|
|
@@ -5,7 +5,7 @@ import * as ui from 'datagrok-api/ui';
|
|
|
5
5
|
import {test, after, before, category, expect} from '@datagrok-libraries/utils/src/test';
|
|
6
6
|
|
|
7
7
|
import {getMonomerLibHelper, IMonomerLibHelper} from '@datagrok-libraries/bio/src/monomer-works/monomer-utils';
|
|
8
|
-
import {LIB_STORAGE_NAME} from '../utils/monomer-lib';
|
|
8
|
+
import {getLibFileNameList, getUserLibSettings, LIB_STORAGE_NAME, setUserLibSetting} from '../utils/monomer-lib';
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
category('monomerLibraries', () => {
|
|
@@ -31,4 +31,17 @@ category('monomerLibraries', () => {
|
|
|
31
31
|
const currentMonomerLib = monomerLibHelper.getBioLib();
|
|
32
32
|
expect(currentMonomerLib.getPolymerTypes().length > 0, true);
|
|
33
33
|
});
|
|
34
|
+
|
|
35
|
+
test('empty', async () => {
|
|
36
|
+
// exclude all monomer libraries for empty set
|
|
37
|
+
const libSettings = await getUserLibSettings();
|
|
38
|
+
const libFnList = await getLibFileNameList();
|
|
39
|
+
libSettings.exclude = libFnList;
|
|
40
|
+
await setUserLibSetting(libSettings);
|
|
41
|
+
|
|
42
|
+
await monomerLibHelper.loadLibraries(true);
|
|
43
|
+
const currentMonomerLib = monomerLibHelper.getBioLib();
|
|
44
|
+
expect(currentMonomerLib.getPolymerTypes().length === 0, true);
|
|
45
|
+
const monomerOfTypesList = currentMonomerLib.getMonomerMolsByPolymerType('PEPTIDE');
|
|
46
|
+
});
|
|
34
47
|
});
|
|
@@ -0,0 +1,47 @@
|
|
|
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 {TAGS as wlTAGS} from '@datagrok-libraries/bio/src/viewers/web-logo';
|
|
6
|
+
import {WebLogoViewer} from '../viewers/web-logo-viewer';
|
|
7
|
+
|
|
8
|
+
import {_package} from '../package';
|
|
9
|
+
import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
|
|
10
|
+
|
|
11
|
+
/** Used in Macromolecule column tooltip */
|
|
12
|
+
export class MacromoleculeColumnWidget extends DG.Widget {
|
|
13
|
+
private viewed: boolean = false;
|
|
14
|
+
|
|
15
|
+
private seqCol: DG.Column<string>;
|
|
16
|
+
|
|
17
|
+
private wlViewer: WebLogoViewer;
|
|
18
|
+
|
|
19
|
+
constructor(seqCol: DG.Column<string>) {
|
|
20
|
+
super(ui.divV([]));
|
|
21
|
+
|
|
22
|
+
this.seqCol = seqCol;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async init(): Promise<void> {
|
|
26
|
+
const uh = UnitsHandler.getOrCreate(this.seqCol);
|
|
27
|
+
const pkgTooltipWebLogo = _package.properties.tooltipWebLogo;
|
|
28
|
+
const colTooltipWebLogo = this.seqCol.getTag(wlTAGS.tooltipWebLogo);
|
|
29
|
+
|
|
30
|
+
if (pkgTooltipWebLogo !== false && !['false', 'off', 'disable', 'disabled'].includes(colTooltipWebLogo)) {
|
|
31
|
+
this.wlViewer = await this.seqCol.dataFrame.plot.fromType('WebLogo', {
|
|
32
|
+
sequenceColumnName: this.seqCol.name,
|
|
33
|
+
backgroundColor: 0x00000000,
|
|
34
|
+
positionHeight: 'Entropy',
|
|
35
|
+
positionWidth: (uh.getAlphabetIsMultichar() ? 24 : 16),
|
|
36
|
+
fixWidth: true,
|
|
37
|
+
fitArea: false,
|
|
38
|
+
// maxHeight: 100,
|
|
39
|
+
// minHeight: 25,
|
|
40
|
+
}) as unknown as WebLogoViewer;
|
|
41
|
+
this.wlViewer.root.style.height = `50px`;
|
|
42
|
+
|
|
43
|
+
this.root.appendChild(this.wlViewer.root);
|
|
44
|
+
this.root.style.width = '100%';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/utils/monomer-lib.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
// import * as ui from 'datagrok-api/ui';
|
|
2
|
-
import * as DG from 'datagrok-api/dg';
|
|
3
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
4
|
|
|
5
5
|
import {Observable, Subject} from 'rxjs';
|
|
6
|
+
|
|
6
7
|
import {IMonomerLib, Monomer} from '@datagrok-libraries/bio/src/types/index';
|
|
7
8
|
import {
|
|
8
9
|
createJsonMonomerLibFromSdf,
|
|
9
10
|
IMonomerLibHelper,
|
|
10
11
|
} from '@datagrok-libraries/bio/src/monomer-works/monomer-utils';
|
|
11
12
|
import {HELM_REQUIRED_FIELDS as REQ, HELM_OPTIONAL_FIELDS as OPT} from '@datagrok-libraries/bio/src/utils/const';
|
|
13
|
+
|
|
12
14
|
import {_package} from '../package';
|
|
13
15
|
|
|
14
16
|
const _HELM_REQUIRED_FIELDS_ARRAY = [
|
|
@@ -37,18 +39,64 @@ export async function getLibFileNameList(): Promise<string[]> {
|
|
|
37
39
|
return res;
|
|
38
40
|
}
|
|
39
41
|
|
|
42
|
+
let userLibSettingsPromise: Promise<void> = Promise.resolve();
|
|
43
|
+
|
|
40
44
|
export async function getUserLibSettings(): Promise<LibSettings> {
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
let res: LibSettings;
|
|
46
|
+
userLibSettingsPromise = userLibSettingsPromise.then(async () => {
|
|
47
|
+
const resStr: string = await grok.dapi.userDataStorage.getValue(LIB_STORAGE_NAME, 'Settings', true);
|
|
48
|
+
res = resStr ? JSON.parse(resStr) : {exclude: []};
|
|
49
|
+
|
|
50
|
+
// Fix empty object returned in case there is no settings stored for user
|
|
51
|
+
res.exclude = res.exclude instanceof Array ? res.exclude : [];
|
|
52
|
+
console.debug(`Bio: getUserLibSettings()\n${JSON.stringify(res, undefined, 2)}`);
|
|
53
|
+
});
|
|
54
|
+
await userLibSettingsPromise;
|
|
55
|
+
return res!;
|
|
56
|
+
}
|
|
43
57
|
|
|
44
|
-
|
|
45
|
-
|
|
58
|
+
export async function setUserLibSetting(value: LibSettings): Promise<void> {
|
|
59
|
+
userLibSettingsPromise = userLibSettingsPromise.then(async () => {
|
|
60
|
+
console.debug(`Bio: setUserLibSettings()\n${JSON.stringify(value, undefined, 2)}`);
|
|
61
|
+
await grok.dapi.userDataStorage.postValue(LIB_STORAGE_NAME, 'Settings', JSON.stringify(value), true);
|
|
62
|
+
});
|
|
63
|
+
await userLibSettingsPromise;
|
|
64
|
+
}
|
|
46
65
|
|
|
47
|
-
|
|
66
|
+
export async function manageFiles() {
|
|
67
|
+
const a = ui.dialog({title: 'Manage files'})
|
|
68
|
+
//@ts-ignore
|
|
69
|
+
.add(ui.fileBrowser({path: 'System:AppData/Bio/libraries'}).root)
|
|
70
|
+
.addButton('OK', () => a.close())
|
|
71
|
+
.show();
|
|
48
72
|
}
|
|
49
73
|
|
|
50
|
-
export async function
|
|
51
|
-
|
|
74
|
+
export async function getLibraryPanelUI(): Promise<DG.Widget> {
|
|
75
|
+
//@ts-ignore
|
|
76
|
+
const filesButton: HTMLButtonElement = ui.button('Manage', manageFiles);
|
|
77
|
+
const inputsForm: HTMLDivElement = ui.inputs([]);
|
|
78
|
+
const libFileNameList: string[] = await getLibFileNameList();
|
|
79
|
+
|
|
80
|
+
const settings = await getUserLibSettings();
|
|
81
|
+
|
|
82
|
+
for (const libFileName of libFileNameList) {
|
|
83
|
+
const libInput: DG.InputBase<boolean | null> = ui.boolInput(libFileName, !settings.exclude.includes(libFileName),
|
|
84
|
+
() => {
|
|
85
|
+
if (libInput.value == true) {
|
|
86
|
+
// Checked library remove from excluded list
|
|
87
|
+
settings.exclude = settings.exclude.filter((l) => l != libFileName);
|
|
88
|
+
} else {
|
|
89
|
+
// Unchecked library add to excluded list
|
|
90
|
+
if (!settings.exclude.includes(libFileName)) settings.exclude.push(libFileName);
|
|
91
|
+
}
|
|
92
|
+
setUserLibSetting(settings).then(async () => {
|
|
93
|
+
await MonomerLibHelper.instance.loadLibraries(true); // from libraryPanel()
|
|
94
|
+
grok.shell.info('Monomer library user settings saved.');
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
inputsForm.append(libInput.root);
|
|
98
|
+
}
|
|
99
|
+
return new DG.Widget(ui.divV([inputsForm, ui.div(filesButton)]));
|
|
52
100
|
}
|
|
53
101
|
|
|
54
102
|
export class MonomerLib implements IMonomerLib {
|
|
@@ -76,7 +124,7 @@ export class MonomerLib implements IMonomerLib {
|
|
|
76
124
|
getMonomerMolsByPolymerType(polymerType: string): { [monomerSymbol: string]: string } {
|
|
77
125
|
const res: { [monomerSymbol: string]: string } = {};
|
|
78
126
|
|
|
79
|
-
Object.keys(this._monomers[polymerType]).forEach((monomerSymbol) => {
|
|
127
|
+
Object.keys(this._monomers[polymerType] ?? {}).forEach((monomerSymbol) => {
|
|
80
128
|
res[monomerSymbol] = this._monomers[polymerType][monomerSymbol].molfile;
|
|
81
129
|
});
|
|
82
130
|
|
|
@@ -154,8 +202,9 @@ export class MonomerLibHelper implements IMonomerLibHelper {
|
|
|
154
202
|
getLibFileNameList(),
|
|
155
203
|
getUserLibSettings(),
|
|
156
204
|
]);
|
|
157
|
-
const
|
|
158
|
-
.filter((libFileName) => !settings.exclude.includes(libFileName))
|
|
205
|
+
const filteredLibFnList = libFileNameList
|
|
206
|
+
.filter((libFileName) => !settings.exclude.includes(libFileName));
|
|
207
|
+
const libs: IMonomerLib[] = await Promise.all(filteredLibFnList
|
|
159
208
|
.map((libFileName) => {
|
|
160
209
|
//TODO handle whether files are in place
|
|
161
210
|
return this.readLibrary(LIB_PATH, libFileName).catch((err: any) => {
|
|
@@ -62,6 +62,7 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
62
62
|
// public sequenceColumnNamePostfix: string;
|
|
63
63
|
|
|
64
64
|
public skipEmptyPositions: boolean;
|
|
65
|
+
/* A value of zero means autofit to the width. */
|
|
65
66
|
public positionWidth: number;
|
|
66
67
|
public positionHeight: PositionHeight;
|
|
67
68
|
|
|
@@ -76,7 +77,10 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
76
77
|
// this.sequenceColumnNamePostfix = this.string('sequenceColumnNamePostfix', 'chain sequence');
|
|
77
78
|
|
|
78
79
|
this.skipEmptyPositions = this.bool('skipEmptyPositions', false);
|
|
79
|
-
this.positionWidth = this.float('positionWidth', 16
|
|
80
|
+
this.positionWidth = this.float('positionWidth', 16, {
|
|
81
|
+
editor: 'slider', min: 0, max: 64,
|
|
82
|
+
description: 'Internal WebLogo viewers property width of position. A value of zero means autofit to the width.'
|
|
83
|
+
});
|
|
80
84
|
this.positionHeight = this.string('positionHeight', PositionHeight.Entropy,
|
|
81
85
|
{choices: Object.keys(PositionHeight)}) as PositionHeight;
|
|
82
86
|
}
|
|
@@ -105,7 +109,6 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
105
109
|
|
|
106
110
|
// this.mlbView.dockManager.dock(this.regionsFg.root, DG.DOCK_TYPE.LEFT, rootNode, 'Filter regions', 0.2);
|
|
107
111
|
|
|
108
|
-
this.subs.push(ui.onSizeChanged(this.root).subscribe(this.rootOnSizeChanged.bind(this)));
|
|
109
112
|
this.subs.push(fromEvent<MouseEvent>(this.root, 'mousemove').subscribe(this.rootOnMouseMove.bind(this)));
|
|
110
113
|
|
|
111
114
|
// await this.buildView('init'); // init
|
|
@@ -213,7 +216,7 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
213
216
|
|
|
214
217
|
private async destroyView(purpose: string): Promise<void> {
|
|
215
218
|
// TODO: Unsubscribe from and remove all view elements
|
|
216
|
-
|
|
219
|
+
_package.logger.debug(`Bio: VdRegionsViewer.destroyView( mainLayout = ${!this.mainLayout ? 'none' : 'value'} ), ` +
|
|
217
220
|
`purpose = '${purpose}'`);
|
|
218
221
|
if (this.filterSourceInput) {
|
|
219
222
|
//
|
|
@@ -232,7 +235,7 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
232
235
|
}
|
|
233
236
|
|
|
234
237
|
private async buildView(purpose: string): Promise<void> {
|
|
235
|
-
|
|
238
|
+
_package.logger.debug(`Bio: VdRegionsViewer.buildView() begin, ` + `purpose = '${purpose}'`);
|
|
236
239
|
|
|
237
240
|
const regionsFiltered: VdRegion[] = this.regions.filter((r: VdRegion) => this.regionTypes.includes(r.type));
|
|
238
241
|
const orderList: number[] = Array.from(new Set(regionsFiltered.map((r) => r.order))).sort();
|
|
@@ -252,6 +255,7 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
252
255
|
positionWidth: this.positionWidth,
|
|
253
256
|
positionHeight: this.positionHeight,
|
|
254
257
|
}) as WebLogoViewer;
|
|
258
|
+
wl.onSizeChanged.subscribe(() => { this.calcSize(); });
|
|
255
259
|
return [orderI, chain, wl];
|
|
256
260
|
})());
|
|
257
261
|
}
|
|
@@ -321,23 +325,59 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
321
325
|
this.root.style.overflowX = 'auto';
|
|
322
326
|
|
|
323
327
|
this.calcSize();
|
|
328
|
+
this.viewSubs.push(ui.onSizeChanged(this.root).subscribe(this.rootOnSizeChanged.bind(this)));
|
|
324
329
|
|
|
325
|
-
|
|
330
|
+
_package.logger.debug('Bio: VdRegionsViewer.buildView() end');
|
|
326
331
|
}
|
|
327
332
|
|
|
328
|
-
private
|
|
329
|
-
const logoHeight = (this.root.clientHeight - 54) / this.chains.length;
|
|
333
|
+
private calcSizeRequested: boolean = false;
|
|
330
334
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
+
private calcSize() {
|
|
336
|
+
_package.logger.debug(`Bio: VdRegionsViewer.calcSize(), start`);
|
|
337
|
+
const calcSizeInt = (): void => {
|
|
338
|
+
const dpr: number = window.devicePixelRatio;
|
|
339
|
+
const logoHeight = (this.root.clientHeight - 54) / this.chains.length;
|
|
340
|
+
|
|
341
|
+
const maxHeight: number = Math.min(logoHeight,
|
|
342
|
+
Math.max(...this.logos.map((wlDict) =>
|
|
343
|
+
Math.max(...Object.values(wlDict).map((wl) => wl.maxHeight)))),
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
let totalPos: number = 0;
|
|
347
|
+
for (let orderI = 0; orderI < this.logos.length; orderI++) {
|
|
348
|
+
for (const chain of this.chains)
|
|
349
|
+
this.logos[orderI][chain].root.style.height = `${maxHeight}px`;
|
|
350
|
+
|
|
351
|
+
totalPos += Math.max(...this.chains.map((chain) => this.logos[orderI][chain].Length));
|
|
352
|
+
}
|
|
335
353
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
const
|
|
339
|
-
|
|
354
|
+
if (this.positionWidth === 0 && this.logos.length > 0 && totalPos > 0) {
|
|
355
|
+
const leftPad = 22/* Chain label */;
|
|
356
|
+
const rightPad = 6 + 6 + 1;
|
|
357
|
+
const logoMargin = 8 + 1;
|
|
358
|
+
const fitPositionWidth =
|
|
359
|
+
(this.root.clientWidth - leftPad - (this.logos.length - 1) * logoMargin - rightPad) / totalPos * dpr;
|
|
360
|
+
|
|
361
|
+
for (let orderI = 0; orderI < this.logos.length; orderI++) {
|
|
362
|
+
for (let chainI = 0; chainI < this.chains.length; chainI++) {
|
|
363
|
+
const chain: string = this.chains[chainI];
|
|
364
|
+
this.logos[orderI][chain].setOptions({positionWidth: fitPositionWidth});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
340
367
|
}
|
|
368
|
+
|
|
369
|
+
if (this.positionWidth === 0)
|
|
370
|
+
this.host!.style.setProperty('overflow-x', 'hidden', 'important');
|
|
371
|
+
else
|
|
372
|
+
this.host!.style.removeProperty('overflow-x');
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
if (!this.calcSizeRequested) {
|
|
376
|
+
this.calcSizeRequested = true;
|
|
377
|
+
window.setTimeout(() => {
|
|
378
|
+
calcSizeInt();
|
|
379
|
+
this.calcSizeRequested = false;
|
|
380
|
+
}, 0 /* next event cycle */);
|
|
341
381
|
}
|
|
342
382
|
}
|
|
343
383
|
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
WebLogoPropsDefault,
|
|
27
27
|
} from '@datagrok-libraries/bio/src/viewers/web-logo';
|
|
28
28
|
import {errorToConsole} from '@datagrok-libraries/utils/src/to-console';
|
|
29
|
+
import {intToHtmlA} from '@datagrok-libraries/utils/src/color';
|
|
29
30
|
|
|
30
31
|
import {_package} from '../package';
|
|
31
32
|
|
|
@@ -155,6 +156,7 @@ export class PositionInfo {
|
|
|
155
156
|
jPos: number, absoluteMaxHeight: number, heightMode: PositionHeight, alphabetSizeLog: number,
|
|
156
157
|
positionWidthWithMargin: number, positionWidth: number, r: number, axisHeight: number
|
|
157
158
|
): void {
|
|
159
|
+
const dpr = window.devicePixelRatio;
|
|
158
160
|
// const rowCount = this.positions[jPos].rowCount;
|
|
159
161
|
// const alphabetSize = this.getAlphabetSize();
|
|
160
162
|
// if ((this.positionHeight == PositionHeight.Entropy) && (alphabetSize == null))
|
|
@@ -207,7 +209,7 @@ export class PositionInfo {
|
|
|
207
209
|
// const m: string = entry[0];
|
|
208
210
|
const h: number = maxHeight * pmInfo.count / this.rowCount;
|
|
209
211
|
|
|
210
|
-
pmInfo.bounds = new DG.Rect(jPos * positionWidthWithMargin, y, positionWidth, h);
|
|
212
|
+
pmInfo.bounds = new DG.Rect(jPos * dpr * positionWidthWithMargin, y, positionWidth * dpr, h);
|
|
211
213
|
y += h;
|
|
212
214
|
}
|
|
213
215
|
}
|
|
@@ -755,9 +757,9 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
755
757
|
|
|
756
758
|
// -- Routines --
|
|
757
759
|
|
|
758
|
-
getMonomer(p: DG.Point): [number, string | null, PositionMonomerInfo | null] {
|
|
759
|
-
const calculatedX = p.x + this.firstVisibleIndex * this.positionWidthWithMargin;
|
|
760
|
-
const jPos = Math.floor(p.x / this.positionWidthWithMargin + this.firstVisibleIndex);
|
|
760
|
+
getMonomer(p: DG.Point, dpr: number): [number, string | null, PositionMonomerInfo | null] {
|
|
761
|
+
const calculatedX = p.x + this.firstVisibleIndex * this.positionWidthWithMargin * dpr;
|
|
762
|
+
const jPos = Math.floor(p.x / (this.positionWidthWithMargin * dpr) + this.firstVisibleIndex);
|
|
761
763
|
const position: PositionInfo = this.positions[jPos];
|
|
762
764
|
|
|
763
765
|
if (position === undefined)
|
|
@@ -887,7 +889,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
887
889
|
|
|
888
890
|
const length: number = this.Length;
|
|
889
891
|
g.resetTransform();
|
|
890
|
-
g.fillStyle =
|
|
892
|
+
g.fillStyle = intToHtmlA(this.backgroundColor);
|
|
891
893
|
g.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
|
892
894
|
g.textBaseline = this.textBaseline;
|
|
893
895
|
|
|
@@ -902,14 +904,15 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
902
904
|
g.textAlign = 'center';
|
|
903
905
|
g.font = `${positionFontSize.toFixed(1)}px Roboto, Roboto Local, sans-serif`;
|
|
904
906
|
const posNameMaxWidth = Math.max(...this.positions.map((pos) => g.measureText(pos.name).width));
|
|
905
|
-
const hScale = posNameMaxWidth < (this._positionWidth - 2) ? 1 :
|
|
907
|
+
const hScale = posNameMaxWidth < (this._positionWidth * dpr - 2) ? 1 :
|
|
908
|
+
(this._positionWidth * dpr - 2) / posNameMaxWidth;
|
|
906
909
|
|
|
907
910
|
for (let jPos = this.firstVisibleIndex; jPos < lastVisibleIndex; jPos++) {
|
|
908
911
|
const pos: PositionInfo = this.positions[jPos];
|
|
909
912
|
g.resetTransform();
|
|
910
913
|
g.setTransform(
|
|
911
914
|
hScale, 0, 0, 1,
|
|
912
|
-
jPos * this.positionWidthWithMargin + this._positionWidth / 2 -
|
|
915
|
+
jPos * this.positionWidthWithMargin * dpr + this._positionWidth * dpr / 2 -
|
|
913
916
|
this.positionWidthWithMargin * firstVisibleIndex, 0);
|
|
914
917
|
g.fillText(pos.label, 0, 0);
|
|
915
918
|
}
|
|
@@ -1097,12 +1100,12 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
1097
1100
|
}
|
|
1098
1101
|
|
|
1099
1102
|
private canvasOnMouseMove(e: MouseEvent) {
|
|
1103
|
+
const dpr = window.devicePixelRatio;
|
|
1100
1104
|
try {
|
|
1101
1105
|
const args = e as MouseEvent;
|
|
1102
1106
|
|
|
1103
|
-
const dpr: number = window.devicePixelRatio;
|
|
1104
1107
|
const cursorP: DG.Point = this.canvas.getCursorPosition(args, dpr);
|
|
1105
|
-
const [jPos, monomer] = this.getMonomer(cursorP);
|
|
1108
|
+
const [jPos, monomer] = this.getMonomer(cursorP, dpr);
|
|
1106
1109
|
// if (jPos != undefined && monomer == undefined) {
|
|
1107
1110
|
// const preEl = ui.element('pre');
|
|
1108
1111
|
// preEl.innerHTML = jPos < this.positions.length ?
|
|
@@ -1133,8 +1136,8 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
1133
1136
|
private canvasOnMouseDown(e: MouseEvent): void {
|
|
1134
1137
|
try {
|
|
1135
1138
|
const args = e as MouseEvent;
|
|
1136
|
-
const
|
|
1137
|
-
const [jPos, monomer] = this.getMonomer(this.canvas.getCursorPosition(args,
|
|
1139
|
+
const dpr: number = window.devicePixelRatio;
|
|
1140
|
+
const [jPos, monomer] = this.getMonomer(this.canvas.getCursorPosition(args, dpr), dpr);
|
|
1138
1141
|
|
|
1139
1142
|
// prevents deselect all rows if we miss monomer bounds
|
|
1140
1143
|
if (this.dataFrame && this.seqCol && this.unitsHandler && monomer) {
|
|
@@ -5,6 +5,7 @@ import {_package} from '../package';
|
|
|
5
5
|
|
|
6
6
|
export class PackageSettingsEditorWidget extends DG.Widget {
|
|
7
7
|
maxMonomerLengthProp: DG.Property;
|
|
8
|
+
tooltipWebLogo: DG.Property;
|
|
8
9
|
|
|
9
10
|
constructor(propList: DG.Property[]) {
|
|
10
11
|
super(ui.div([], {}));
|
|
@@ -13,6 +14,7 @@ export class PackageSettingsEditorWidget extends DG.Widget {
|
|
|
13
14
|
Object.assign({}, ...propList.map((p) => ({[p.name]: p})));
|
|
14
15
|
|
|
15
16
|
this.maxMonomerLengthProp = props['MaxMonomerLength'];
|
|
17
|
+
this.tooltipWebLogo = props['TooltipWebLogo'];
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
async init(): Promise<void> {
|
|
@@ -23,6 +25,14 @@ export class PackageSettingsEditorWidget extends DG.Widget {
|
|
|
23
25
|
_package.properties.maxMonomerLength = value;
|
|
24
26
|
});
|
|
25
27
|
|
|
26
|
-
|
|
28
|
+
const tooltipWebLogo = ui.boolInput('Tooltip WebLogo',
|
|
29
|
+
_package.properties.tooltipWebLogo,
|
|
30
|
+
(value: boolean) => {
|
|
31
|
+
_package.properties.tooltipWebLogo = value;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
this.root.appendChild(ui.form([
|
|
35
|
+
maxMonomerLengthInput,
|
|
36
|
+
tooltipWebLogo]));
|
|
27
37
|
}
|
|
28
38
|
}
|