@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.
Files changed (34) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/package-test.js +1 -1
  3. package/dist/package-test.js.map +1 -1
  4. package/dist/package.js +1 -1
  5. package/dist/package.js.map +1 -1
  6. package/files/polytool-rules/rules_example.json +34 -0
  7. package/package.json +4 -3
  8. package/src/apps/pattern/model/event-bus.ts +23 -6
  9. package/src/apps/pattern/model/translator.ts +27 -1
  10. package/src/apps/pattern/view/components/bulk-convert/column-input.ts +13 -3
  11. package/src/apps/pattern/view/components/bulk-convert/table-controls.ts +4 -3
  12. package/src/apps/pattern/view/components/load-block-controls.ts +4 -2
  13. package/src/apps/pattern/view/svg-utils/const.ts +15 -1
  14. package/src/apps/pattern/view/svg-utils/legend-block.ts +92 -0
  15. package/src/apps/pattern/view/svg-utils/strands-block.ts +335 -0
  16. package/src/apps/pattern/view/svg-utils/svg-block-base.ts +37 -0
  17. package/src/apps/pattern/view/svg-utils/svg-display-manager.ts +4 -5
  18. package/src/apps/pattern/view/svg-utils/svg-element-factory.ts +16 -4
  19. package/src/apps/pattern/view/svg-utils/svg-renderer.ts +32 -377
  20. package/src/apps/pattern/view/svg-utils/text-dimensions-calculator.ts +29 -0
  21. package/src/apps/pattern/view/svg-utils/title-block.ts +53 -0
  22. package/src/apps/translator/view/ui.ts +1 -1
  23. package/src/package.ts +22 -6
  24. package/src/polytool/const.ts +3 -15
  25. package/src/polytool/pt-conversion.ts +307 -0
  26. package/src/polytool/pt-dialog.ts +115 -0
  27. package/src/polytool/pt-enumeration.ts +127 -0
  28. package/src/polytool/pt-rules.ts +73 -0
  29. package/src/polytool/utils.ts +7 -0
  30. package/src/tests/helm-to-nucleotides.ts +0 -5
  31. package/tsconfig.json +1 -1
  32. package/src/apps/pattern/view/svg-utils/dimensions-calculator.ts +0 -498
  33. package/src/polytool/transformation.ts +0 -326
  34. 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.2.9",
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.40.3",
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.2",
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 _selectedColumn = new rxjs.BehaviorSubject<{strand: string, colName: string} | null>(null);
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
- selectColumn(strand: StrandType, colName: string) {
463
- this._selectedColumn.next({strand, colName});
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
- getSelectedColumn(strand: StrandType): string | null {
467
- return this._selectedColumn.getValue()?.colName ?? null;
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 {TERMINI, TERMINUS} from './const';
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.selectColumn(strand, colName)
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('ID column', columns[0], columns, () => { });
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 {STRAND} from '../../../model/const';
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 convertSequenceButton = ui.bigButton('Convert', () => this.processConvertButtonClick());
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
- convertSequenceButton,
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 (!choiceInput.value?.includes(patternName))
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((patternName) => patternName.includes(this.eventBus.getPatternName())) ?? patternList[0];
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 = 14,
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
+ }