@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/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "name": "Leonid Stolbov",
6
6
  "email": "lstolbov@datagrok.ai"
7
7
  },
8
- "version": "2.6.1",
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": {
@@ -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 {MonomerLibHelper, getUserLibSettings, setUserLibSetting, getLibFileNameList} from './utils/monomer-lib';
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 async function sequenceTooltip(col: DG.Column): Promise<DG.Widget<any>> {
130
- const tv = grok.shell.tv;
131
- const viewer = await tv.dataFrame.plot.fromType('WebLogo', {
132
- sequenceColumnName: col.name,
133
- backgroundColor: 0xFFfdffe5,
134
- fitArea: false,
135
- positionHeight: 'Entropy',
136
- fixWidth: true,
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
- //@ts-ignore
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
+ }
@@ -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
- const resStr: string = await grok.dapi.userDataStorage.getValue(LIB_STORAGE_NAME, 'Settings', true);
42
- const res: LibSettings = resStr ? JSON.parse(resStr) : {exclude: []};
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
- // Fix empty object returned in case there is no settings stored for user
45
- res.exclude = res.exclude instanceof Array ? res.exclude : [];
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
- return res;
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 setUserLibSetting(value: LibSettings): Promise<void> {
51
- await grok.dapi.userDataStorage.postValue(LIB_STORAGE_NAME, 'Settings', JSON.stringify(value), true);
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 libs: IMonomerLib[] = await Promise.all(libFileNameList
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
- console.debug(`Bio: VdRegionsViewer.destroyView( mainLayout = ${!this.mainLayout ? 'none' : 'value'} ), ` +
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
- console.debug(`Bio: VdRegionsViewer.buildView() begin, ` + `purpose = '${purpose}'`);
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
- console.debug('Bio: VdRegionsViewer.buildView() end');
330
+ _package.logger.debug('Bio: VdRegionsViewer.buildView() end');
326
331
  }
327
332
 
328
- private calcSize() {
329
- const logoHeight = (this.root.clientHeight - 54) / this.chains.length;
333
+ private calcSizeRequested: boolean = false;
330
334
 
331
- const maxHeight: number = Math.min(logoHeight,
332
- Math.max(...this.logos.map((wlDict) =>
333
- Math.max(...Object.values(wlDict).map((wl) => wl.maxHeight)))),
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
- for (let orderI = 0; orderI < this.logos.length; orderI++) {
337
- for (let chainI = 0; chainI < this.chains.length; chainI++) {
338
- const chain: string = this.chains[chainI];
339
- this.logos[orderI][chain].root.style.height = `${maxHeight}px`;
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 = DG.Color.toHtml(this.backgroundColor);
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 : (this._positionWidth - 2) / posNameMaxWidth;
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 r: number = window.devicePixelRatio;
1137
- const [jPos, monomer] = this.getMonomer(this.canvas.getCursorPosition(args, r));
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
- this.root.appendChild(ui.form([maxMonomerLengthInput,]));
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
  }