@datagrok/sequence-translator 1.2.9 → 1.3.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/CHANGELOG.md +13 -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/files/polytool-rules/rules_example.json +34 -0
- package/package.json +4 -3
- package/src/apps/pattern/model/event-bus.ts +23 -6
- package/src/apps/pattern/model/translator.ts +27 -1
- package/src/apps/pattern/view/components/bulk-convert/column-input.ts +13 -3
- package/src/apps/pattern/view/components/bulk-convert/table-controls.ts +4 -3
- package/src/apps/pattern/view/components/load-block-controls.ts +4 -2
- package/src/apps/pattern/view/svg-utils/const.ts +15 -1
- package/src/apps/pattern/view/svg-utils/legend-block.ts +92 -0
- package/src/apps/pattern/view/svg-utils/strands-block.ts +335 -0
- package/src/apps/pattern/view/svg-utils/svg-block-base.ts +37 -0
- package/src/apps/pattern/view/svg-utils/svg-display-manager.ts +4 -5
- package/src/apps/pattern/view/svg-utils/svg-element-factory.ts +16 -4
- package/src/apps/pattern/view/svg-utils/svg-renderer.ts +32 -377
- package/src/apps/pattern/view/svg-utils/text-dimensions-calculator.ts +29 -0
- package/src/apps/pattern/view/svg-utils/title-block.ts +53 -0
- package/src/apps/translator/view/ui.ts +1 -1
- package/src/package.ts +22 -6
- package/src/polytool/const.ts +3 -15
- package/src/polytool/pt-conversion.ts +307 -0
- package/src/polytool/pt-dialog.ts +115 -0
- package/src/polytool/pt-enumeration.ts +127 -0
- package/src/polytool/pt-rules.ts +73 -0
- package/src/polytool/utils.ts +7 -0
- package/src/tests/helm-to-nucleotides.ts +0 -5
- package/tsconfig.json +1 -1
- package/src/apps/pattern/view/svg-utils/dimensions-calculator.ts +0 -498
- package/src/polytool/transformation.ts +0 -326
- package/src/polytool/ui.ts +0 -59
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"type": "link",
|
|
4
|
+
"code": "1",
|
|
5
|
+
"monomericSubstitution": {
|
|
6
|
+
"firstMonomer": "C",
|
|
7
|
+
"secondMonomer": "C",
|
|
8
|
+
"firstLinkingGroup":"3",
|
|
9
|
+
"secondLinkingGroup":"3",
|
|
10
|
+
"firstSubstitution": "C",
|
|
11
|
+
"secondSubstitution": "C"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"type": "link",
|
|
16
|
+
"code": "2",
|
|
17
|
+
"monomericSubstitution": {
|
|
18
|
+
"firstMonomer": "NH2",
|
|
19
|
+
"secondMonomer": "L",
|
|
20
|
+
"firstLinkingGroup":"2",
|
|
21
|
+
"secondLinkingGroup":"3",
|
|
22
|
+
"firstSubstitution": "NH2",
|
|
23
|
+
"secondSubstitution": "L"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"type": "fragmentDuplication",
|
|
28
|
+
"code": "#3"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"type": "differentFragments",
|
|
32
|
+
"code": "$4"
|
|
33
|
+
}
|
|
34
|
+
]
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datagrok/sequence-translator",
|
|
3
3
|
"friendlyName": "Sequence Translator",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.3.1",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Alexey Choposky",
|
|
7
7
|
"email": "achopovsky@datagrok.ai"
|
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
"directory": "packages/SequenceTranslator"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@datagrok-libraries/bio": "^5.
|
|
16
|
+
"@datagrok-libraries/bio": "^5.41.9",
|
|
17
17
|
"@datagrok-libraries/chem-meta": "^1.2.3",
|
|
18
18
|
"@datagrok-libraries/tutorials": "^1.3.12",
|
|
19
19
|
"@datagrok-libraries/utils": "^4.1.45",
|
|
20
20
|
"@types/react": "^18.0.15",
|
|
21
21
|
"cash-dom": "^8.1.0",
|
|
22
|
-
"datagrok-api": "^1.18.
|
|
22
|
+
"datagrok-api": "^1.18.6",
|
|
23
23
|
"lodash": "^4.17.21",
|
|
24
24
|
"object-hash": "^3.0.0",
|
|
25
25
|
"openchemlib": "6.0.1",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
},
|
|
52
52
|
"scripts": {
|
|
53
53
|
"link-api": "npm link datagrok-api",
|
|
54
|
+
"link-bio": "npm link @datagrok-libraries/bio",
|
|
54
55
|
"link-all": "npm link @datagrok-libraries/chem-meta datagrok-api @datagrok-libraries/utils @datagrok-libraries/bio @datagrok-libraries/tutorials",
|
|
55
56
|
"debug-sequencetranslator": "grok publish",
|
|
56
57
|
"release-sequencetranslator": "grok publish localhost --release",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* Do not change these import lines to match external modules in webpack configuration */
|
|
2
2
|
import * as DG from 'datagrok-api/dg';
|
|
3
3
|
import * as rxjs from 'rxjs';
|
|
4
|
-
import {debounceTime, map, skip, switchMap} from 'rxjs/operators';
|
|
4
|
+
import {debounceTime, throttleTime, map, skip, switchMap} from 'rxjs/operators';
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
7
|
GRAPH_SETTINGS_KEYS as G, LEGEND_SETTINGS_KEYS as L, PATTERN_RECORD_KEYS as R, STRAND, STRANDS, TERMINI, TERMINUS
|
|
@@ -49,7 +49,9 @@ export class EventBus {
|
|
|
49
49
|
private _patternHasUnsavedChanges$ = new rxjs.BehaviorSubject<boolean>(false);
|
|
50
50
|
private _lastLoadedPatternConfig: rxjs.BehaviorSubject<PatternConfiguration >;
|
|
51
51
|
|
|
52
|
-
private
|
|
52
|
+
private _selectedStrandColumn = new rxjs.BehaviorSubject<{[strand: string]: string | null} | null>(null);
|
|
53
|
+
private _selectedIdColumn = new rxjs.BehaviorSubject<string | null>(null);
|
|
54
|
+
|
|
53
55
|
|
|
54
56
|
constructor(
|
|
55
57
|
private dataManager: DataManager,
|
|
@@ -459,12 +461,27 @@ export class EventBus {
|
|
|
459
461
|
return this._patternHasUnsavedChanges$.asObservable();
|
|
460
462
|
}
|
|
461
463
|
|
|
462
|
-
|
|
463
|
-
this.
|
|
464
|
+
selectStrandColumn(strand: StrandType, colName: string | null) {
|
|
465
|
+
this._selectedStrandColumn.next({...this._selectedStrandColumn.getValue(), [strand]: colName});
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
getSelectedStrandColumn(strand: StrandType): string | null {
|
|
469
|
+
const value = this._selectedStrandColumn.getValue();
|
|
470
|
+
return value ? value[strand] : null;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
selectIdColumn(colName: string) {
|
|
474
|
+
this._selectedIdColumn.next(colName);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
getSelectedIdColumn(): string | null {
|
|
478
|
+
return this._selectedIdColumn.getValue();
|
|
464
479
|
}
|
|
465
480
|
|
|
466
|
-
|
|
467
|
-
return this.
|
|
481
|
+
get updateSvgContainer$(): rxjs.Observable<void> {
|
|
482
|
+
return this.patternStateChanged$.pipe(
|
|
483
|
+
debounceTime(100)
|
|
484
|
+
);
|
|
468
485
|
}
|
|
469
486
|
}
|
|
470
487
|
|
|
@@ -1,5 +1,31 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as grok from 'datagrok-api/grok';
|
|
2
|
+
import {STRAND, STRANDS, TERMINI, TERMINUS} from './const';
|
|
2
3
|
import {PATTERN_APP_DATA} from '../../common/model/data-loader/json-loader';
|
|
4
|
+
import {EventBus} from './event-bus';
|
|
5
|
+
|
|
6
|
+
export function bulkTranslate(eventBus: EventBus): void {
|
|
7
|
+
const df = eventBus.getTableSelection();
|
|
8
|
+
if (!df) {
|
|
9
|
+
grok.shell.warning('Please select a table');
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const strandColNames = STRANDS.filter(
|
|
13
|
+
(strand) => !(strand === STRAND.ANTISENSE && !eventBus.isAntisenseStrandActive())
|
|
14
|
+
).map((strand) => eventBus.getSelectedStrandColumn(strand))
|
|
15
|
+
.filter((colName) => colName) as string[];
|
|
16
|
+
|
|
17
|
+
if (strandColNames.length === 0) {
|
|
18
|
+
grok.shell.warning('Please column for sense strand');
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const idColumnName = eventBus.getSelectedIdColumn();
|
|
23
|
+
if (!idColumnName) throw new Error('No ID column selected');
|
|
24
|
+
|
|
25
|
+
const idColumn = df.getCol(idColumnName);
|
|
26
|
+
|
|
27
|
+
const strandCols = strandColNames.map((colName) => df.getCol(colName));
|
|
28
|
+
}
|
|
3
29
|
|
|
4
30
|
export function applyPatternToRawSequence(
|
|
5
31
|
rawNucleotideSequence: string,
|
|
@@ -27,7 +27,6 @@ export class ColumnInputManager {
|
|
|
27
27
|
|
|
28
28
|
private handleTableChoice(): void {
|
|
29
29
|
this.refreshColumnControls();
|
|
30
|
-
// grok.shell.info(`Table ${this.selectedTable?.name} selection from column input manager`);
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
private refreshColumnControls(): void {
|
|
@@ -40,6 +39,10 @@ export class ColumnInputManager {
|
|
|
40
39
|
const senseStrandColumnInput = strandColumnInput[STRAND.SENSE];
|
|
41
40
|
const antisenseStrandColumnInput = strandColumnInput[STRAND.ANTISENSE];
|
|
42
41
|
|
|
42
|
+
this.eventBus.antisenseStrandToggled$.subscribe((isAntisenseActive) => {
|
|
43
|
+
$(antisenseStrandColumnInput).toggle(isAntisenseActive);
|
|
44
|
+
});
|
|
45
|
+
|
|
43
46
|
const idColumnInput = this.createIdColumnInput();
|
|
44
47
|
|
|
45
48
|
return [senseStrandColumnInput, antisenseStrandColumnInput, idColumnInput];
|
|
@@ -54,8 +57,9 @@ export class ColumnInputManager {
|
|
|
54
57
|
`${STRAND_LABEL[strand]} column`,
|
|
55
58
|
columns[0],
|
|
56
59
|
columns,
|
|
57
|
-
(colName: string) => this.eventBus.
|
|
60
|
+
(colName: string) => this.eventBus.selectStrandColumn(strand, colName)
|
|
58
61
|
);
|
|
62
|
+
this.eventBus.selectStrandColumn(strand, columns[0]);
|
|
59
63
|
return [strand, input.root];
|
|
60
64
|
})) as Record<StrandType, HTMLElement>;
|
|
61
65
|
return strandColumnInput;
|
|
@@ -63,7 +67,13 @@ export class ColumnInputManager {
|
|
|
63
67
|
|
|
64
68
|
private createIdColumnInput(): HTMLElement {
|
|
65
69
|
const columns = this.selectedTable ? this.selectedTable.columns.names() : [];
|
|
66
|
-
const idColumnInput = ui.choiceInput(
|
|
70
|
+
const idColumnInput = ui.choiceInput(
|
|
71
|
+
'ID column',
|
|
72
|
+
columns[0],
|
|
73
|
+
columns,
|
|
74
|
+
(colName: string) => this.eventBus.selectIdColumn(colName)
|
|
75
|
+
);
|
|
76
|
+
this.eventBus.selectIdColumn(columns[0]);
|
|
67
77
|
return idColumnInput.root;
|
|
68
78
|
}
|
|
69
79
|
}
|
|
@@ -4,7 +4,7 @@ import '../../style.css';
|
|
|
4
4
|
import {EventBus} from '../../../model/event-bus';
|
|
5
5
|
import {ColumnInputManager} from './column-input';
|
|
6
6
|
import {TableInputManager} from './table-input';
|
|
7
|
-
import {
|
|
7
|
+
import {bulkTranslate} from '../../../model/translator';
|
|
8
8
|
|
|
9
9
|
export class TableControlsManager {
|
|
10
10
|
private tableInputManager: TableInputManager;
|
|
@@ -20,18 +20,19 @@ export class TableControlsManager {
|
|
|
20
20
|
const tableInput = this.tableInputManager.getTableInputContainer();
|
|
21
21
|
const columnControls = this.columnInputManager.getColumnControlsContainer();
|
|
22
22
|
|
|
23
|
-
const
|
|
23
|
+
const convertButton = ui.bigButton('Convert', () => this.processConvertButtonClick());
|
|
24
24
|
|
|
25
25
|
return [
|
|
26
26
|
title,
|
|
27
27
|
tableInput,
|
|
28
28
|
columnControls,
|
|
29
29
|
ui.buttonsInput([
|
|
30
|
-
|
|
30
|
+
convertButton,
|
|
31
31
|
]),
|
|
32
32
|
];
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
private processConvertButtonClick(): void {
|
|
36
|
+
bulkTranslate(this.eventBus);
|
|
36
37
|
}
|
|
37
38
|
}
|
|
@@ -153,7 +153,7 @@ export class PatternLoadControlsManager {
|
|
|
153
153
|
this.subscriptions.add(
|
|
154
154
|
this.eventBus.patternLoaded$.subscribe(() => {
|
|
155
155
|
const patternName = this.eventBus.getPatternName();
|
|
156
|
-
if (
|
|
156
|
+
if (choiceInput.value !== patternName)
|
|
157
157
|
choiceInput.value = this.getPatternName(patternList);
|
|
158
158
|
})
|
|
159
159
|
);
|
|
@@ -162,7 +162,9 @@ export class PatternLoadControlsManager {
|
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
private getPatternName(patternList: string[]): string {
|
|
165
|
-
return patternList.find(
|
|
165
|
+
return patternList.find(
|
|
166
|
+
(patternName) => patternName === this.eventBus.getPatternName()
|
|
167
|
+
) ?? patternList[0];
|
|
166
168
|
}
|
|
167
169
|
|
|
168
170
|
private createDeletePatternButton(): HTMLButtonElement {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {STRAND, TERMINUS, STRAND_END} from '../../model/const';
|
|
2
2
|
|
|
3
|
+
export const LEGEND_PADDING = 10;
|
|
4
|
+
|
|
3
5
|
export const enum LUMINANCE_COEFFICIENTS {
|
|
4
6
|
RED = 0.299,
|
|
5
7
|
GREEN = 0.587,
|
|
@@ -21,7 +23,7 @@ export const enum SVG_CIRCLE_SIZES {
|
|
|
21
23
|
|
|
22
24
|
export const enum SVG_TEXT_FONT_SIZES {
|
|
23
25
|
NUCLEOBASE = 17,
|
|
24
|
-
COMMENT =
|
|
26
|
+
COMMENT = 15,
|
|
25
27
|
};
|
|
26
28
|
|
|
27
29
|
export const enum SVG_ELEMENT_COLORS {
|
|
@@ -61,3 +63,15 @@ export const Y_POSITIONS_FOR_STRAND_ELEMENTS = {
|
|
|
61
63
|
NUCLEOBASE_LABEL: 7 * SVG_CIRCLE_SIZES.NUCLEOBASE_RADIUS,
|
|
62
64
|
}
|
|
63
65
|
};
|
|
66
|
+
|
|
67
|
+
export const MIN_WIDTH = 200;
|
|
68
|
+
|
|
69
|
+
export const TITLE_SHIFT = 10;
|
|
70
|
+
|
|
71
|
+
export const enum FONT_SIZE {
|
|
72
|
+
TITLE = 17,
|
|
73
|
+
LEGEND = 14,
|
|
74
|
+
NUCLEOBASE = 17,
|
|
75
|
+
COMMENT = 14,
|
|
76
|
+
}
|
|
77
|
+
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import {PatternConfiguration} from '../../model/types';
|
|
2
|
+
import {LEGEND_PADDING, SVG_CIRCLE_SIZES, SVG_ELEMENT_COLORS, SVG_TEXT_FONT_SIZES} from './const';
|
|
3
|
+
import {SVGBlockBase} from './svg-block-base';
|
|
4
|
+
import {SVGElementFactory} from './svg-element-factory';
|
|
5
|
+
import {TextDimensionsCalculator} from './text-dimensions-calculator';
|
|
6
|
+
import {getNucleobaseColorFromStyleMap} from './utils';
|
|
7
|
+
|
|
8
|
+
export class LegendBlock extends SVGBlockBase {
|
|
9
|
+
private _svgElements: SVGElement[] = [];
|
|
10
|
+
private width: number;
|
|
11
|
+
|
|
12
|
+
constructor(
|
|
13
|
+
svgElementFactory: SVGElementFactory,
|
|
14
|
+
config: PatternConfiguration,
|
|
15
|
+
heightShift: number
|
|
16
|
+
) {
|
|
17
|
+
super(svgElementFactory, config, heightShift);
|
|
18
|
+
const {elements, width} = this.createLegendItems();
|
|
19
|
+
this._svgElements = elements;
|
|
20
|
+
this.width = width;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private isPhosphorothioatePresent(): boolean {
|
|
24
|
+
return Object.values(this.config.phosphorothioateLinkageFlags)
|
|
25
|
+
.flat().some((element) => element);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private getModificationTypesList(): string[] {
|
|
29
|
+
return [...new Set(
|
|
30
|
+
Object.values(this.config.nucleotideSequences).flat().sort(
|
|
31
|
+
(a, b) => a.toLowerCase().localeCompare(b.toLowerCase())
|
|
32
|
+
)
|
|
33
|
+
)];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get svgElements(): SVGElement[] {
|
|
37
|
+
return this._svgElements;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private createLegendItem(
|
|
41
|
+
name: string,
|
|
42
|
+
xShift: number
|
|
43
|
+
): {elements: SVGElement[], width: number} {
|
|
44
|
+
const circlePosition = {
|
|
45
|
+
x: xShift,
|
|
46
|
+
y: this.yShift - SVG_CIRCLE_SIZES.LEGEND_RADIUS
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const circle = name.includes('linkage') ?
|
|
50
|
+
this.svgElementFactory.createStarElement(circlePosition, SVG_ELEMENT_COLORS.LINKAGE_STAR) :
|
|
51
|
+
this.svgElementFactory
|
|
52
|
+
.createCircleElement(circlePosition, SVG_CIRCLE_SIZES.LEGEND_RADIUS, getNucleobaseColorFromStyleMap(name));
|
|
53
|
+
const paddedCircleWidth = 2 * SVG_CIRCLE_SIZES.LEGEND_RADIUS;
|
|
54
|
+
xShift += paddedCircleWidth;
|
|
55
|
+
|
|
56
|
+
const textPosition = {y: this.yShift, x: xShift};
|
|
57
|
+
|
|
58
|
+
const textElement = this.svgElementFactory
|
|
59
|
+
.createTextElement(name, textPosition, SVG_TEXT_FONT_SIZES.COMMENT, SVG_ELEMENT_COLORS.TEXT);
|
|
60
|
+
const textWidth = TextDimensionsCalculator.getTextDimensions(name, SVG_TEXT_FONT_SIZES.COMMENT).width;
|
|
61
|
+
|
|
62
|
+
return {elements: [circle, textElement], width: paddedCircleWidth + textWidth};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private createLegendItems(): { elements: SVGElement[], width: number } {
|
|
66
|
+
let xShift = LEGEND_PADDING;
|
|
67
|
+
const items = [] as SVGElement[][];
|
|
68
|
+
|
|
69
|
+
const shift = (width: number) => xShift += width + 15;
|
|
70
|
+
|
|
71
|
+
if (this.isPhosphorothioatePresent()) {
|
|
72
|
+
const {elements, width} = this.createLegendItem('PTO linkage', xShift);
|
|
73
|
+
shift(width);
|
|
74
|
+
items.push(elements);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const modificationTypes = this.getModificationTypesList();
|
|
78
|
+
modificationTypes.forEach((name) => {
|
|
79
|
+
const {elements, width} = this.createLegendItem(name, xShift);
|
|
80
|
+
shift(width);
|
|
81
|
+
items.push(elements);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return {elements: items.flat(), width: xShift};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
getContentHeight(): number { return 20; };
|
|
88
|
+
|
|
89
|
+
getContentWidth(): number {
|
|
90
|
+
return this.width;
|
|
91
|
+
}
|
|
92
|
+
}
|