@datagrok/bio 2.11.40 → 2.11.42

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.11.40",
8
+ "version": "2.11.42",
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",
package/src/const.ts ADDED
@@ -0,0 +1,5 @@
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
+ export const GAP_SYMBOL: string = '';
@@ -11,6 +11,10 @@ import {
11
11
  } from '../viewers/web-logo-viewer';
12
12
  import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
13
13
 
14
+ import {GAP_SYMBOL} from '../const';
15
+
16
+ const g: string = GAP_SYMBOL;
17
+
14
18
  category('WebLogo-positions', () => {
15
19
  const csvDf1 = `seq
16
20
  ATC-G-TTGC--
@@ -36,18 +40,18 @@ ATC-G-TTGC--
36
40
  const positions: PI[] = wlViewer['positions'];
37
41
 
38
42
  const resAllDf1: PI[] = [
39
- new PI(0, '1', {'A': new PMI(2), '-': new PMI(3)}),
43
+ new PI(0, '1', {'A': new PMI(2), [g]: new PMI(3)}),
40
44
  new PI(1, '2', {'T': new PMI(5)}),
41
45
  new PI(2, '3', {'C': new PMI(5)}),
42
- new PI(3, '4', {'-': new PMI(5)}),
46
+ new PI(3, '4', {[g]: new PMI(5)}),
43
47
  new PI(4, '5', {'G': new PMI(5)}),
44
- new PI(5, '6', {'-': new PMI(3), 'C': new PMI(2)}),
48
+ new PI(5, '6', {[g]: new PMI(3), 'C': new PMI(2)}),
45
49
  new PI(6, '7', {'T': new PMI(5)}),
46
50
  new PI(7, '8', {'T': new PMI(5)}),
47
51
  new PI(8, '9', {'G': new PMI(5)}),
48
52
  new PI(9, '10', {'C': new PMI(5)}),
49
- new PI(10, '11', {'-': new PMI(5)}),
50
- new PI(11, '12', {'-': new PMI(5)}),
53
+ new PI(10, '11', {[g]: new PMI(5)}),
54
+ new PI(11, '12', {[g]: new PMI(5)}),
51
55
  ];
52
56
 
53
57
  expect(positions.length, resAllDf1.length);
@@ -89,15 +93,15 @@ ATC-G-TTGC--
89
93
  const positions: PI[] = wlViewer['positions'];
90
94
 
91
95
  const resAllDf1: PI[] = [
92
- new PI(0, '1', {'-': new PMI(3)}),
96
+ new PI(0, '1', {[g]: new PMI(3)}),
93
97
  new PI(1, '2', {'T': new PMI(3)}),
94
- new PI(2, '3', {'-': new PMI(3)}),
95
- new PI(3, '4', {'-': new PMI(3)}),
98
+ new PI(2, '3', {[g]: new PMI(3)}),
99
+ new PI(3, '4', {[g]: new PMI(3)}),
96
100
  new PI(4, '5', {'C': new PMI(3)}),
97
- new PI(5, '6', {'-': new PMI(2), 'C': new PMI(1)}),
101
+ new PI(5, '6', {[g]: new PMI(2), 'C': new PMI(1)}),
98
102
  new PI(6, '7', {'G': new PMI(3)}),
99
103
  new PI(7, '8', {'T': new PMI(3)}),
100
- new PI(8, '9', {'-': new PMI(3)}),
104
+ new PI(8, '9', {[g]: new PMI(3)}),
101
105
  ];
102
106
 
103
107
  expect(positions.length, resAllDf1.length);
@@ -128,11 +132,11 @@ ATC-G-TTGC--
128
132
  const resPosList: PI[] = wlViewer['positions'];
129
133
 
130
134
  const tgtPosList: PI[] = [
131
- new PI(0, '1', {'A': new PMI(2), '-': new PMI(3)}),
135
+ new PI(0, '1', {'A': new PMI(2), [g]: new PMI(3)}),
132
136
  new PI(1, '2', {'T': new PMI(5)}),
133
137
  new PI(2, '3', {'C': new PMI(5)}),
134
138
  new PI(4, '5', {'G': new PMI(5)}),
135
- new PI(5, '6', {'-': new PMI(3), 'C': new PMI(2)}),
139
+ new PI(5, '6', {[g]: new PMI(3), 'C': new PMI(2)}),
136
140
  new PI(6, '7', {'T': new PMI(5)}),
137
141
  new PI(7, '8', {'T': new PMI(5)}),
138
142
  new PI(8, '9', {'G': new PMI(5)}),
@@ -166,7 +170,7 @@ ATC-G-TTGC--
166
170
  const tgtPosList: PI[] = [
167
171
  new PI(2, '3', {'C': new PMI(5)}),
168
172
  new PI(4, '5', {'G': new PMI(5)}),
169
- new PI(5, '6', {'-': new PMI(3), 'C': new PMI(2)}),
173
+ new PI(5, '6', {[g]: new PMI(3), 'C': new PMI(2)}),
170
174
  new PI(6, '7', {'T': new PMI(5)}),
171
175
  ];
172
176
 
@@ -16,6 +16,11 @@ import {UnknownSeqPalette} from '@datagrok-libraries/bio/src/unknown';
16
16
  import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
17
17
  import {getStatsForCol} from '@datagrok-libraries/bio/src/utils/macromolecule/utils';
18
18
 
19
+ import {GAP_SYMBOL} from '../const';
20
+
21
+ /** GAP_SYMBOL */
22
+ const g: string = GAP_SYMBOL;
23
+
19
24
  category('bio', () => {
20
25
  const csvDfN1: string = `seq
21
26
  ACGTCT
@@ -144,7 +149,7 @@ export async function _testGetAlphabetSimilarity() {
144
149
  'C': 3015,
145
150
  'G': 3015,
146
151
  'T': 2048,
147
- '-': 1000,
152
+ [g]: 1000,
148
153
  };
149
154
  const alphabet: Set<string> = new Set(Object.keys(Nucleotides.Names));
150
155
  const res = getAlphabetSimilarity(freq, alphabet);
@@ -1,11 +1,11 @@
1
1
  /* Do not change these import lines to match external modules in webpack configuration */
2
2
  import * as grok from 'datagrok-api/grok';
3
- import * as ui from 'datagrok-api/ui';
4
3
  import * as DG from 'datagrok-api/dg';
4
+ import * as OCL from 'openchemlib/full';
5
5
 
6
6
  import {MolfileHandler} from '@datagrok-libraries/chem-meta/src/parsing-utils/molfile-handler';
7
7
  import {MolfileHandlerBase} from '@datagrok-libraries/chem-meta/src/parsing-utils/molfile-handler-base';
8
- import {RDModule} from '@datagrok-libraries/chem-meta/src/rdkit-api';
8
+ import {RDModule, RDMol} from '@datagrok-libraries/chem-meta/src/rdkit-api';
9
9
  import {HELM_POLYMER_TYPE, HELM_RGROUP_FIELDS} from '@datagrok-libraries/bio/src/utils/const';
10
10
  import {errInfo} from '@datagrok-libraries/bio/src/utils/err-info';
11
11
 
@@ -51,10 +51,10 @@ export async function helm2mol(df: DG.DataFrame, helmCol: DG.Column<string>): Pr
51
51
 
52
52
  /** Translate HELM column into molfile column and append to the dataframe */
53
53
  export async function getMolColumnFromHelm(
54
- df: DG.DataFrame, helmCol: DG.Column<string>
54
+ df: DG.DataFrame, helmCol: DG.Column<string>, chiralityEngine?: boolean
55
55
  ): Promise<DG.Column<string>> {
56
56
  const converter = new HelmToMolfileConverter(helmCol, df);
57
- const molCol = await converter.convertToRdKitBeautifiedMolfileColumn();
57
+ const molCol = await converter.convertToRdKitBeautifiedMolfileColumn(chiralityEngine);
58
58
  molCol.semType = DG.SEMTYPE.MOLECULE;
59
59
  return molCol;
60
60
  }
@@ -89,7 +89,28 @@ export class HelmToMolfileConverter {
89
89
  return smiles;
90
90
  }
91
91
 
92
- async convertToRdKitBeautifiedMolfileColumn(): Promise<DG.Column<string>> {
92
+ async getMolV3000ViaOCL(beautifiedMols: (RDMol | null)[], columnName: string) {
93
+ const beautifiedMolV2000 = beautifiedMols.map((mol) => {
94
+ if (mol === null)
95
+ return '';
96
+ const molBlock = mol.get_molblock();
97
+ mol!.delete();
98
+ return molBlock;
99
+ });
100
+ const molv3000Arr = new Array<string>(beautifiedMolV2000.length);
101
+ const chiralityPb = DG.TaskBarProgressIndicator.create(`Handling chirality...`);
102
+ for (let i = 0; i < beautifiedMolV2000.length; i++) {
103
+ const oclMolecule = OCL.Molecule.fromMolfile(beautifiedMolV2000[i]);
104
+ const molV3000 = oclMolecule.toMolfileV3();
105
+ molv3000Arr[i] = molV3000.replace('STERAC1', 'STEABS');
106
+ const progress = i/beautifiedMolV2000.length*100;
107
+ chiralityPb.update(progress, `${progress?.toFixed(2)}% of molecules completed`);
108
+ }
109
+ chiralityPb.close();
110
+ return DG.Column.fromStrings(columnName, molv3000Arr);
111
+ }
112
+
113
+ async convertToRdKitBeautifiedMolfileColumn(chiralityEngine?: boolean): Promise<DG.Column<string>> {
93
114
  const smiles = await this.getSmilesList();
94
115
  const rdKitModule: RDModule = await grok.functions.call('Chem:getRdKitModule');
95
116
  const beautifiedMols = smiles.map((item) =>{
@@ -103,13 +124,16 @@ export class HelmToMolfileConverter {
103
124
  return mol;
104
125
  });
105
126
  const columnName = this.df.columns.getUnusedName(`molfile(${this.helmColumn.name})`);
127
+
128
+ if (chiralityEngine)
129
+ return await this.getMolV3000ViaOCL(beautifiedMols, columnName);
106
130
  return DG.Column.fromStrings(columnName, beautifiedMols.map((mol) => {
107
131
  if (mol === null)
108
132
  return '';
109
133
  const molBlock = mol.get_v3Kmolblock();
110
134
  mol!.delete();
111
135
  return molBlock;
112
- }));
136
+ }));
113
137
  }
114
138
 
115
139
  async convertToMolfileV2KColumn(): Promise<DG.Column<string>> {
@@ -14,7 +14,7 @@ export class MacromoleculeColumnWidget extends DG.Widget {
14
14
 
15
15
  private readonly seqCol: DG.Column<string>;
16
16
 
17
- private wlViewer: WebLogoViewer;
17
+ private wlViewer: WebLogoViewer | null = null;
18
18
 
19
19
  constructor(seqCol: DG.Column<string>) {
20
20
  super(ui.divV([]));
@@ -46,7 +46,10 @@ export class MacromoleculeColumnWidget extends DG.Widget {
46
46
  }
47
47
 
48
48
  override detach() {
49
- this.wlViewer.detach();
49
+ if (this.wlViewer) {
50
+ this.wlViewer.detach();
51
+ this.wlViewer = null;
52
+ }
50
53
  super.detach();
51
54
  }
52
55
  }
@@ -221,7 +221,7 @@ function getHelmCycle(helm: string, source: ConnectionData): string {
221
221
  }
222
222
 
223
223
  export async function addTransformedColumn(
224
- molColumn: DG.Column<string>, addHelm: boolean, ruleFiles: string[]
224
+ molColumn: DG.Column<string>, addHelm: boolean, ruleFiles: string[], chiralityEngine?: boolean
225
225
  ): Promise<void> {
226
226
  const df = molColumn.dataFrame;
227
227
  const uh = UnitsHandler.getOrCreate(molColumn);
@@ -243,7 +243,7 @@ export async function addTransformedColumn(
243
243
  addCommonTags(targetHelmCol);
244
244
  targetHelmCol.setTag('units', NOTATION.HELM);
245
245
 
246
- const molCol = await getMolColumnFromHelm(df, targetHelmCol);
246
+ const molCol = await getMolColumnFromHelm(df, targetHelmCol, chiralityEngine);
247
247
  molCol.name = df.columns.getUnusedName('molfile(' + molColumn.name + ')');
248
248
 
249
249
  if (addHelm) {
@@ -121,10 +121,12 @@ export class PolyTool {
121
121
  const addButton = this.getAddButton();
122
122
  this.ruleFilesInputs = ui.div(await this.getRuleFilesBlock());
123
123
  //const rulesFiles = ui.div(this.ruleFilesInputs);
124
+ const chiralityEngineInput = ui.boolInput('Chirality engine', false);
124
125
 
125
126
  const div = ui.div([
126
127
  targetColumnInput,
127
128
  generateHelmChoiceInput,
129
+ chiralityEngineInput,
128
130
  'Rules used',
129
131
  this.ruleFilesInputs,
130
132
  addButton
@@ -138,7 +140,7 @@ export class PolyTool {
138
140
  grok.shell.warning('No marcomolecule column chosen!');
139
141
  return;
140
142
  }
141
- addTransformedColumn(molCol!, generateHelmChoiceInput.value!, this.userRuleSettings.included);
143
+ addTransformedColumn(molCol!, generateHelmChoiceInput.value!, this.userRuleSettings.included, chiralityEngineInput.value!);
142
144
  });
143
145
 
144
146
  return this.dialog;
@@ -26,6 +26,7 @@ import {AggFunc, getAgg} from '../utils/agg';
26
26
  import {buildCompositionTable} from '../widgets/composition-analysis-widget';
27
27
 
28
28
  import {_package} from '../package';
29
+ import {GAP_SYMBOL} from '../const';
29
30
 
30
31
  declare global {
31
32
  interface HTMLCanvasElement {
@@ -81,12 +82,6 @@ export class PositionMonomerInfo {
81
82
  }
82
83
 
83
84
  export class PositionInfo {
84
- /** Position in sequence */
85
- public readonly pos: number;
86
-
87
- /** Position name from column tag*/
88
- public readonly name: string;
89
-
90
85
  private readonly _label: string | undefined;
91
86
  public get label(): string { return !!this._label ? this._label : this.name; }
92
87
 
@@ -105,11 +100,12 @@ export class PositionInfo {
105
100
  * @param {number} rowCount Count of elements in column
106
101
  * @param {number} sumForHeightCalc Sum of all monomer counts for height calculation
107
102
  */
108
- constructor(pos: number, name: string, freqs?: { [m: string]: PositionMonomerInfo },
103
+ constructor(
104
+ /** Position in sequence */ public readonly pos: number,
105
+ /** Position name from column tag*/ public readonly name: string,
106
+ freqs?: { [m: string]: PositionMonomerInfo },
109
107
  options?: { sumRowCount?: number, sumValueForHeight?: number, label?: string }
110
108
  ) {
111
- this.pos = pos;
112
- this.name = name;
113
109
  this._freqs = freqs ?? {};
114
110
 
115
111
  if (options?.sumRowCount) this.sumRowCount = options.sumRowCount;
@@ -240,6 +236,8 @@ export class PositionInfo {
240
236
  }
241
237
 
242
238
  buildCompositionTable(palette: SeqPalette): HTMLTableElement {
239
+ if ('-' in this._freqs)
240
+ throw new Error(`Unexpected monomer symbol '-'.`);
243
241
  return buildCompositionTable(palette,
244
242
  Object.assign({}, ...Object.entries(this._freqs)
245
243
  .map(([m, pmi]) => ({[m]: pmi.rowCount})))
@@ -616,13 +614,13 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
616
614
  private getFilter(): DG.BitSet {
617
615
  let dfFilterRes: DG.BitSet;
618
616
  switch (this.filterSource) {
619
- case FilterSources.Filtered:
620
- dfFilterRes = this.dataFrame.filter;
621
- break;
617
+ case FilterSources.Filtered:
618
+ dfFilterRes = this.dataFrame.filter;
619
+ break;
622
620
 
623
- case FilterSources.Selected:
624
- dfFilterRes = this.dataFrame.selection.trueCount === 0 ? this.dataFrame.filter : this.dataFrame.selection;
625
- break;
621
+ case FilterSources.Selected:
622
+ dfFilterRes = this.dataFrame.selection.trueCount === 0 ? this.dataFrame.filter : this.dataFrame.selection;
623
+ break;
626
624
  }
627
625
  return dfFilterRes;
628
626
  }
@@ -816,45 +814,42 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
816
814
  super.onPropertyChanged(property);
817
815
 
818
816
  switch (property.name) {
819
- case PROPS.sequenceColumnName:
820
- this.updateSeqCol();
821
- break;
822
- case PROPS.sequenceColumnName:
823
- case PROPS.startPositionName:
824
- case PROPS.endPositionName:
825
- case PROPS.filterSource:
826
- case PROPS.shrinkEmptyTail:
827
- case PROPS.skipEmptyPositions:
828
- case PROPS.positionHeight: {
829
- this.render(WlRenderLevel.Freqs, `onPropertyChanged( ${property.name} )`);
830
- break;
831
- }
832
-
833
- case PROPS.valueColumnName:
834
- case PROPS.valueAggrType: {
835
- this.render(WlRenderLevel.Freqs, `onPropertyChanged( ${property.name} )`);
836
- break;
837
- }
817
+ case PROPS.sequenceColumnName:
818
+ this.updateSeqCol();
819
+ break;
820
+ case PROPS.startPositionName:
821
+ case PROPS.endPositionName:
822
+ case PROPS.filterSource:
823
+ case PROPS.shrinkEmptyTail:
824
+ case PROPS.skipEmptyPositions:
825
+ case PROPS.positionHeight: {
826
+ this.render(WlRenderLevel.Freqs, `onPropertyChanged( ${property.name} )`);
827
+ break;
828
+ }
829
+ case PROPS.valueColumnName:
830
+ case PROPS.valueAggrType: {
831
+ this.render(WlRenderLevel.Freqs, `onPropertyChanged( ${property.name} )`);
832
+ break;
833
+ }
834
+ case PROPS.minHeight:
835
+ case PROPS.maxHeight:
836
+ case PROPS.positionWidth:
837
+ case PROPS.showPositionLabels:
838
+ case PROPS.fixWidth:
839
+ case PROPS.fitArea:
840
+ case PROPS.horizontalAlignment:
841
+ case PROPS.verticalAlignment:
842
+ case PROPS.positionMargin:
843
+ case PROPS.positionMarginState: {
838
844
  // this.positionWidth obtains a new value
839
845
  // this.updateSlider updates this._positionWidth
840
- case PROPS.minHeight:
841
- case PROPS.maxHeight:
842
- case PROPS.positionWidth:
843
- case PROPS.showPositionLabels:
844
- case PROPS.fixWidth:
845
- case PROPS.fitArea:
846
- case PROPS.horizontalAlignment:
847
- case PROPS.verticalAlignment:
848
- case PROPS.positionMargin:
849
- case PROPS.positionMarginState: {
850
- this.render(WlRenderLevel.Layout, `onPropertyChanged(${property.name})`);
851
- break;
852
- }
853
-
854
- case PROPS.backgroundColor: {
855
- this.render(WlRenderLevel.Render, `onPropertyChanged(${property.name})`);
856
- break;
857
- }
846
+ this.render(WlRenderLevel.Layout, `onPropertyChanged(${property.name})`);
847
+ break;
848
+ }
849
+ case PROPS.backgroundColor: {
850
+ this.render(WlRenderLevel.Render, `onPropertyChanged(${property.name})`);
851
+ break;
852
+ }
858
853
  }
859
854
  }
860
855
 
@@ -928,8 +923,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
928
923
  protected _removeEmptyPositions() {
929
924
  if (this.skipEmptyPositions) {
930
925
  this.positions = wu(this.positions).filter((pi) => {
931
- const gapSymbol: string = this.unitsHandler!.defaultGapSymbol;
932
- return !pi.hasMonomer(gapSymbol) || pi.getFreq(gapSymbol).rowCount !== pi.sumRowCount;
926
+ return !pi.hasMonomer(GAP_SYMBOL) || pi.getFreq(GAP_SYMBOL).rowCount !== pi.sumRowCount;
933
927
  }).toArray();
934
928
  }
935
929
  }
@@ -1004,9 +998,10 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
1004
998
  for (let rowI = 0; rowI < dfRowCount; ++rowI) {
1005
999
  if (dfFilter.get(rowI)) {
1006
1000
  const seqMList: ISeqSplitted = splitted[rowI];
1007
- const m: string = seqMList[this.startPosition + jPos] || this.unitsHandler.defaultGapSymbol;
1001
+ const om: string = seqMList[this.startPosition + jPos] || this.unitsHandler.defaultGapSymbol;
1002
+ const cm: string = this.unitsHandler?.defaultGapSymbol === om ? GAP_SYMBOL : om;
1008
1003
  const pi = this.positions[jPos];
1009
- const pmi = pi.getFreq(m);
1004
+ const pmi = pi.getFreq(cm);
1010
1005
  ++pi.sumRowCount;
1011
1006
  pmi.value = ++pmi.rowCount;
1012
1007
  }
@@ -1078,9 +1073,8 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
1078
1073
  if (this.seqCol && !this.palette) {
1079
1074
  this.msgHost!.innerText = `Unknown palette (column semType: '${this.seqCol.semType}').`;
1080
1075
  this.msgHost!.style.display = '';
1081
- } else {
1076
+ } else
1082
1077
  this.msgHost!.style.display = 'none';
1083
- }
1084
1078
  }
1085
1079
 
1086
1080
  if (!this.seqCol || !this.dataFrame || !this.palette || this.host == null || this.slider == null)
@@ -1233,9 +1227,8 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
1233
1227
  tooltipRows.push(ui.div(`${this.valueAggrType}: ${pmi.value.toFixed(3)}`));
1234
1228
  const tooltipEl = ui.divV(tooltipRows);
1235
1229
  ui.tooltip.show(tooltipEl, args.x + 16, args.y + 16);
1236
- } else {
1230
+ } else
1237
1231
  ui.tooltip.hide();
1238
- }
1239
1232
  } catch (err: any) {
1240
1233
  const errMsg = errorToConsole(err);
1241
1234
  _package.logger.error(`Bio: WebLogoViewer<${this.viewerId}>.canvasOnMouseMove() error:\n` + errMsg);
@@ -9,6 +9,7 @@ import {SeqPalette} from '@datagrok-libraries/bio/src/seq-palettes';
9
9
  import {UnknownSeqPalettes} from '@datagrok-libraries/bio/src/unknown';
10
10
  import '../../css/composition-analysis.css';
11
11
  import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
12
+ import {GAP_SYMBOL} from '../const';
12
13
 
13
14
 
14
15
  export function getCompositionAnalysisWidget(val: DG.SemanticValue): DG.Widget {
@@ -17,15 +18,15 @@ export function getCompositionAnalysisWidget(val: DG.SemanticValue): DG.Widget {
17
18
  const alphabet = val.cell.column.tags[bioTAGS.alphabet];
18
19
  let palette: SeqPalette = UnknownSeqPalettes.Color;
19
20
  switch (alphabet) {
20
- case ALPHABET.DNA:
21
- case ALPHABET.RNA:
22
- palette = getPaletteByType(ALPHABET.DNA);
23
- break;
24
- case ALPHABET.PT:
25
- palette = getPaletteByType(ALPHABET.PT);
26
- break;
27
- default:
28
- break;
21
+ case ALPHABET.DNA:
22
+ case ALPHABET.RNA:
23
+ palette = getPaletteByType(ALPHABET.DNA);
24
+ break;
25
+ case ALPHABET.PT:
26
+ palette = getPaletteByType(ALPHABET.PT);
27
+ break;
28
+ default:
29
+ break;
29
30
  }
30
31
 
31
32
  const counts: { [m: string]: number } = {};
@@ -57,15 +58,21 @@ export function buildCompositionTable(palette: SeqPalette, counts: { [m: string]
57
58
  const maxRatio = maxValue! / sumValue;
58
59
  const elMap: { [m: string]: HTMLElement } = Object.assign({}, ...Array.from(Object.entries(counts))
59
60
  .sort((a, b) => b[1] - a[1])
60
- .map(([m, value]) => {
61
+ .map(([cm, value]) => {
61
62
  const ratio = value / sumValue;
62
- const color = palette.get(m);
63
+ const color = palette.get(cm);
63
64
  const barDiv = ui.div('', {classes: 'macromolecule-cell-comp-analysis-bar'});
64
65
  barDiv.style.width = `${50 * ratio / maxRatio}px`;
65
66
  barDiv.style.backgroundColor = color;
67
+ if (GAP_SYMBOL === cm) {
68
+ barDiv.style.borderWidth = '1px';
69
+ barDiv.style.borderStyle = 'solid';
70
+ barDiv.style.borderColor = DG.Color.toHtml(DG.Color.lightGray);
71
+ }
72
+ const displayMonomer: string = GAP_SYMBOL === cm ? '-' : cm;
66
73
  const valueDiv = ui.div(`${(100 * ratio).toFixed(2)}%`);
67
74
  const el = ui.div([barDiv, valueDiv], {classes: 'macromolecule-cell-comp-analysis-value'});
68
- return ({[m]: el});
75
+ return ({[displayMonomer]: el});
69
76
  }));
70
77
 
71
78
  const table = ui.tableFromMap(elMap);