@datagrok/peptides 0.4.3 → 0.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.
@@ -4,13 +4,20 @@ import * as DG from 'datagrok-api/dg';
4
4
 
5
5
  import $ from 'cash-dom';
6
6
 
7
- import {model} from './model';
8
-
7
+ import {model} from '../model';
8
+
9
+ /**
10
+ * Structure-activity relationship viewer.
11
+ *
12
+ * @export
13
+ * @class SARViewer
14
+ * @extends {DG.JsViewer}
15
+ */
9
16
  export class SARViewer extends DG.JsViewer {
10
17
  protected viewerGrid: DG.Grid | null;
11
18
  protected sourceGrid: DG.Grid | null;
12
- protected activityColumnColumnName: string;
13
- protected activityScalingMethod: string;
19
+ protected activityColumnName: string;
20
+ protected scaling: string;
14
21
  protected bidirectionalAnalysis: boolean;
15
22
  protected filterMode: boolean;
16
23
  protected statsDf: DG.DataFrame | null;
@@ -25,6 +32,12 @@ export class SARViewer extends DG.JsViewer {
25
32
  // protected pValueThreshold: number;
26
33
  // protected amountOfBestAARs: number;
27
34
  // duplicatesHandingMethod: string;
35
+
36
+ /**
37
+ * Creates an instance of SARViewer.
38
+ *
39
+ * @memberof SARViewer
40
+ */
28
41
  constructor() {
29
42
  super();
30
43
 
@@ -38,9 +51,9 @@ export class SARViewer extends DG.JsViewer {
38
51
  this.viewGridInitialized = false;
39
52
  this.currentBitset = null;
40
53
 
41
- //TODO: find a way to restrict activityColumnColumnName to accept only numerical columns (double even better)
42
- this.activityColumnColumnName = this.string('activityColumnColumnName');
43
- this.activityScalingMethod = this.string('activityScalingMethod', 'none', {choices: ['none', 'lg', '-lg']});
54
+ //TODO: find a way to restrict activityColumnName to accept only numerical columns (double even better)
55
+ this.activityColumnName = this.string('activityColumnName');
56
+ this.scaling = this.string('scaling', 'none', {choices: ['none', 'lg', '-lg']});
44
57
  this.filterMode = this.bool('filterMode', false);
45
58
  this.bidirectionalAnalysis = this.bool('bidirectionalAnalysis', false);
46
59
  this.grouping = this.bool('grouping', false);
@@ -51,8 +64,14 @@ export class SARViewer extends DG.JsViewer {
51
64
  this.sourceGrid = null;
52
65
  }
53
66
 
67
+ /**
68
+ * Initializes SARViewer.
69
+ *
70
+ * @memberof SARViewer
71
+ */
54
72
  init() {
55
73
  this._initialBitset = this.dataFrame!.filter.clone();
74
+ this.currentBitset = this._initialBitset.clone();
56
75
  this.initialized = true;
57
76
  this.subs.push(model.statsDf$.subscribe((data) => this.statsDf = data));
58
77
  this.subs.push(model.viewerGrid$.subscribe((data) => {
@@ -63,16 +82,32 @@ export class SARViewer extends DG.JsViewer {
63
82
  this.subs.push(model.groupMapping$.subscribe((data) => this.groupMapping = data));
64
83
  }
65
84
 
85
+ /**
86
+ * Function that is executed when the table is attached.
87
+ *
88
+ * @memberof SARViewer
89
+ */
66
90
  onTableAttached() {
67
91
  this.sourceGrid = this.view.grid;
68
92
  this.sourceGrid?.dataFrame?.setTag('dataType', 'peptides');
69
93
  this.render();
70
94
  }
71
95
 
96
+ /**
97
+ * Function that is executed when the viewer is detached from the table.
98
+ *
99
+ * @memberof SARViewer
100
+ */
72
101
  detach() {
73
102
  this.subs.forEach((sub) => sub.unsubscribe());
74
103
  }
75
104
 
105
+ /**
106
+ * Function that is executed when the property is changed.
107
+ *
108
+ * @param {DG.Property} property New property.
109
+ * @memberof SARViewer
110
+ */
76
111
  onPropertyChanged(property: DG.Property) {
77
112
  super.onPropertyChanged(property);
78
113
 
@@ -81,14 +116,14 @@ export class SARViewer extends DG.JsViewer {
81
116
  return;
82
117
  }
83
118
 
84
- if (property.name === 'activityScalingMethod' && typeof this.dataFrame !== 'undefined') {
119
+ if (property.name === 'scaling' && typeof this.dataFrame !== 'undefined') {
85
120
  const minActivity = DG.Stats.fromColumn(
86
- this.dataFrame!.col(this.activityColumnColumnName)!,
121
+ this.dataFrame!.col(this.activityColumnName)!,
87
122
  this._initialBitset,
88
123
  ).min;
89
- if (minActivity && minActivity <= 0 && this.activityScalingMethod !== 'none') {
90
- grok.shell.warning(`Could not apply ${this.activityScalingMethod}: ` +
91
- `activity column ${this.activityColumnColumnName} contains zero or negative values, falling back to 'none'.`);
124
+ if (minActivity && minActivity <= 0 && this.scaling !== 'none') {
125
+ grok.shell.warning(`Could not apply ${this.scaling}: ` +
126
+ `activity column ${this.activityColumnName} contains zero or negative values, falling back to 'none'.`);
92
127
  property.set(this, 'none');
93
128
  return;
94
129
  }
@@ -97,167 +132,23 @@ export class SARViewer extends DG.JsViewer {
97
132
  this.render();
98
133
  }
99
134
 
100
- private applyBitset() {
101
- if (
102
- this.dataFrame &&
103
- this.viewerGrid &&
104
- this.viewerGrid.dataFrame &&
105
- this.viewerGrid.dataFrame.currentCell.value &&
106
- this.viewerGrid.dataFrame.currentCol.name !== this.aminoAcidResidue
107
- ) {
108
- const currentAAR: string =
109
- this.viewerGrid.dataFrame.get(this.aminoAcidResidue, this.viewerGrid.dataFrame.currentRowIdx);
110
- const currentPosition = this.viewerGrid.dataFrame.currentCol.name;
111
-
112
- const splitColName = '~splitCol';
113
- const otherLabel = 'Other';
114
- const aarLabel = `${currentAAR === '-' ? 'Empty' : currentAAR} - ${currentPosition}`;
115
-
116
- let splitCol = this.dataFrame.col(splitColName);
117
- if (!splitCol) {
118
- splitCol = this.dataFrame.columns.addNew(splitColName, 'string');
119
- }
120
-
121
- const isChosen = (i: number) => this.groupMapping![this.dataFrame!.get(currentPosition, i)] === currentAAR;
122
- splitCol!.init((i) => isChosen(i) ? aarLabel : otherLabel);
123
-
124
- //TODO: use column.compact
125
- this.currentBitset = DG.BitSet.create(this.dataFrame.rowCount, isChosen).and(this._initialBitset!);
126
- this.sourceFilteringFunc();
127
-
128
- const colorMap: {[index: string]: string | number} = {};
129
- colorMap[otherLabel] = DG.Color.blue;
130
- colorMap[aarLabel] = DG.Color.orange;
131
- // colorMap[currentAAR] = cp.getColor(currentAAR);
132
- this.dataFrame.getCol(splitColName).colors.setCategorical(colorMap);
133
- }
134
- }
135
-
136
- private sourceFilteringFunc() {
137
- if (this.filterMode) {
138
- this.dataFrame!.selection.setAll(false, false);
139
- this.dataFrame!.filter.copyFrom(this.currentBitset!);
140
- } else {
141
- this.dataFrame!.filter.copyFrom(this._initialBitset!);
142
- this.dataFrame!.selection.copyFrom(this.currentBitset!);
143
- }
144
- }
145
-
146
- private accordionFunc(accordion: DG.Accordion) {
147
- if (accordion.context instanceof DG.RowGroup) {
148
- const originalDf: DG.DataFrame = DG.toJs(accordion.context.dataFrame);
149
- const viewerDf = this.viewerGrid?.dataFrame;
150
-
151
- if (
152
- originalDf.getTag('dataType') === 'peptides' &&
153
- originalDf.col('~splitCol') &&
154
- this.viewerGrid &&
155
- viewerDf &&
156
- viewerDf.currentCol !== null
157
- ) {
158
- const currentAAR: string = viewerDf.get(
159
- this.aminoAcidResidue,
160
- viewerDf.currentRowIdx,
161
- );
162
- const currentPosition = viewerDf.currentCol.name;
163
-
164
- const labelStr = `${currentAAR === '-' ? 'Empty' : currentAAR} - ${currentPosition}`;
165
- const currentColor = DG.Color.toHtml(DG.Color.orange);
166
- const otherColor = DG.Color.toHtml(DG.Color.blue);
167
- const currentLabel = ui.label(labelStr, {style: {color: currentColor}});
168
- const otherLabel = ui.label('Other', {style: {color: otherColor}});
169
-
170
- const elements: (HTMLLabelElement | HTMLElement)[] = [currentLabel, otherLabel];
171
-
172
- const distPane = accordion.getPane('Distribution');
173
- if (distPane) {
174
- accordion.removePane(distPane);
175
- }
176
- accordion.addPane('Distribution', () => {
177
- const hist = originalDf.clone(this._initialBitset).plot.histogram({
178
- // const hist = originalDf.plot.histogram({
179
- filteringEnabled: false,
180
- valueColumnName: `${this.activityColumnColumnName}Scaled`,
181
- splitColumnName: '~splitCol',
182
- legendVisibility: 'Never',
183
- showXAxis: true,
184
- showColumnSelector: false,
185
- showRangeSlider: false,
186
- }).root;
187
- hist.style.width = 'auto';
188
- elements.push(hist);
189
-
190
- const tableMap: {[key: string]: string} = {'Statistics:': ''};
191
- for (const colName of new Set(['Count', 'pValue', 'Mean difference'])) {
192
- const query = `${this.aminoAcidResidue} = ${currentAAR} and Position = ${currentPosition}`;
193
- const textNum = this.statsDf?.groupBy([colName]).where(query).aggregate().get(colName, 0);
194
- // const text = textNum === 0 ? '<0.01' : `${colName === 'Count' ? textNum : textNum.toFixed(2)}`;
195
- const text = colName === 'Count' ? `${textNum}` : textNum < 0.01 ? '<0.01' : textNum.toFixed(2);
196
- tableMap[colName === 'pValue' ? 'p-value' : colName] = text;
197
- }
198
- elements.push(ui.tableFromMap(tableMap));
199
-
200
- return ui.divV(elements);
201
- }, true);
202
- }
203
- }
204
- }
205
-
206
- syncGridsFunc(sourceVertical: boolean) { //TODO: refactor
207
- if (this.viewerGrid && this.viewerGrid.dataFrame && this.viewerVGrid && this.viewerVGrid.dataFrame) {
208
- if (sourceVertical) {
209
- const dfCell = this.viewerVGrid.dataFrame.currentCell;
210
- if (dfCell.column === null || dfCell.column.name !== 'Mean difference') {
211
- return;
212
- }
213
- const otherColName: string = this.viewerVGrid.dataFrame.get('Position', dfCell.rowIndex);
214
- const otherRowName: string = this.viewerVGrid.dataFrame.get(this.aminoAcidResidue, dfCell.rowIndex);
215
- let otherRowIndex = -1;
216
- for (let i = 0; i < this.viewerGrid.dataFrame.rowCount; i++) {
217
- if (this.viewerGrid.dataFrame.get(this.aminoAcidResidue, i) === otherRowName) {
218
- otherRowIndex = i;
219
- break;
220
- }
221
- }
222
- if (otherRowIndex !== -1) {
223
- this.viewerGrid.dataFrame.currentCell = this.viewerGrid.dataFrame.cell(otherRowIndex, otherColName);
224
- }
225
- } else {
226
- const otherPos: string = this.viewerGrid.dataFrame.currentCol?.name;
227
- if (typeof otherPos === 'undefined' && otherPos !== this.aminoAcidResidue) {
228
- return;
229
- }
230
- const otherAAR: string =
231
- this.viewerGrid.dataFrame.get(this.aminoAcidResidue, this.viewerGrid.dataFrame.currentRowIdx);
232
- let otherRowIndex = -1;
233
- for (let i = 0; i < this.viewerVGrid.dataFrame.rowCount; i++) {
234
- if (
235
- this.viewerVGrid.dataFrame.get(this.aminoAcidResidue, i) === otherAAR &&
236
- this.viewerVGrid.dataFrame.get('Position', i) === otherPos
237
- ) {
238
- otherRowIndex = i;
239
- break;
240
- }
241
- }
242
- if (otherRowIndex !== -1) {
243
- this.viewerVGrid.dataFrame.currentCell = this.viewerVGrid.dataFrame.cell(otherRowIndex, 'Mean difference');
244
- }
245
- }
246
- }
247
- }
248
- // argument compute data can be used to just redraw grids.
249
- // Probably iirelevant since mostly grids are updating themselves, and render is only used to update data.
135
+ /**
136
+ * Viewer render function.
137
+ *
138
+ * @param {boolean} [computeData=true] Recalculate data.
139
+ * @memberof SARViewer
140
+ */
250
141
  async render(computeData = true) {
251
142
  if (!this.initialized) {
252
143
  return;
253
144
  }
254
145
  //TODO: optimize. Don't calculate everything again if only view changes
255
146
  if (computeData) {
256
- if (typeof this.dataFrame !== 'undefined' && this.activityColumnColumnName && this.sourceGrid) {
147
+ if (typeof this.dataFrame !== 'undefined' && this.activityColumnName && this.sourceGrid) {
257
148
  await model?.updateData(
258
149
  this.dataFrame!,
259
- this.activityColumnColumnName,
260
- this.activityScalingMethod,
150
+ this.activityColumnName,
151
+ this.scaling,
261
152
  this.sourceGrid,
262
153
  this.bidirectionalAnalysis,
263
154
  this._initialBitset,
@@ -268,12 +159,24 @@ export class SARViewer extends DG.JsViewer {
268
159
  $(this.root).empty();
269
160
  this.root.appendChild(this.viewerGrid.root);
270
161
  this.viewerGrid.dataFrame!.onCurrentCellChanged.subscribe((_) => {
271
- this.applyBitset();
272
- this.syncGridsFunc(false);
162
+ this.currentBitset = applyBitset(
163
+ this.dataFrame!, this.viewerGrid!, this.aminoAcidResidue,
164
+ this.groupMapping!, this._initialBitset!, this.filterMode,
165
+ ) ?? this.currentBitset;
166
+ syncGridsFunc(false, this.viewerGrid!, this.viewerVGrid!, this.aminoAcidResidue);
167
+ });
168
+ this.viewerVGrid.dataFrame!.onCurrentCellChanged.subscribe((_) => {
169
+ syncGridsFunc(true, this.viewerGrid!, this.viewerVGrid!, this.aminoAcidResidue);
170
+ });
171
+ this.dataFrame!.onRowsFiltering.subscribe((_) => {
172
+ sourceFilteringFunc(this.filterMode, this.dataFrame!, this.currentBitset!, this._initialBitset!);
173
+ });
174
+ grok.events.onAccordionConstructed.subscribe((accordion: DG.Accordion) => {
175
+ accordionFunc(
176
+ accordion, this.viewerGrid!, this.aminoAcidResidue,
177
+ this._initialBitset!, this.activityColumnName, this.statsDf!,
178
+ );
273
179
  });
274
- this.viewerVGrid.dataFrame!.onCurrentCellChanged.subscribe((_) => this.syncGridsFunc(true));
275
- this.dataFrame!.onRowsFiltering.subscribe((_) => this.sourceFilteringFunc());
276
- grok.events.onAccordionConstructed.subscribe((accordion: DG.Accordion) => this.accordionFunc(accordion));
277
180
  }
278
181
  }
279
182
  }
@@ -282,8 +185,21 @@ export class SARViewer extends DG.JsViewer {
282
185
  }
283
186
  }
284
187
 
188
+ /**
189
+ * Vertical structure activity relationship viewer.
190
+ *
191
+ * @export
192
+ * @class SARViewerVertical
193
+ * @extends {DG.JsViewer}
194
+ */
285
195
  export class SARViewerVertical extends DG.JsViewer {
286
196
  viewerVGrid: DG.Grid | null;
197
+
198
+ /**
199
+ * Creates an instance of SARViewerVertical.
200
+ *
201
+ * @memberof SARViewerVertical
202
+ */
287
203
  constructor() {
288
204
  super();
289
205
 
@@ -294,6 +210,11 @@ export class SARViewerVertical extends DG.JsViewer {
294
210
  }));
295
211
  }
296
212
 
213
+ /**
214
+ * Viewer render function.
215
+ *
216
+ * @memberof SARViewerVertical
217
+ */
297
218
  render() {
298
219
  if (this.viewerVGrid) {
299
220
  $(this.root).empty();
@@ -302,3 +223,175 @@ export class SARViewerVertical extends DG.JsViewer {
302
223
  this.viewerVGrid?.invalidate();
303
224
  }
304
225
  }
226
+
227
+ function syncGridsFunc(
228
+ sourceVertical: boolean,
229
+ viewerGrid: DG.Grid,
230
+ viewerVGrid: DG.Grid,
231
+ aminoAcidResidue: string,
232
+ ) { //TODO: refactor, move
233
+ if (viewerGrid && viewerGrid.dataFrame && viewerVGrid && viewerVGrid.dataFrame) {
234
+ if (sourceVertical) {
235
+ const dfCell = viewerVGrid.dataFrame.currentCell;
236
+ if (dfCell.column === null || dfCell.column.name !== 'Mean difference') {
237
+ return;
238
+ }
239
+ const otherColName: string = viewerVGrid.dataFrame.get('Position', dfCell.rowIndex);
240
+ const otherRowName: string = viewerVGrid.dataFrame.get(aminoAcidResidue, dfCell.rowIndex);
241
+ let otherRowIndex = -1;
242
+ for (let i = 0; i < viewerGrid.dataFrame.rowCount; i++) {
243
+ if (viewerGrid.dataFrame.get(aminoAcidResidue, i) === otherRowName) {
244
+ otherRowIndex = i;
245
+ break;
246
+ }
247
+ }
248
+ if (otherRowIndex !== -1) {
249
+ viewerGrid.dataFrame.currentCell = viewerGrid.dataFrame.cell(otherRowIndex, otherColName);
250
+ }
251
+ } else {
252
+ const otherPos: string = viewerGrid.dataFrame.currentCol?.name;
253
+ if (typeof otherPos === 'undefined' && otherPos !== aminoAcidResidue) {
254
+ return;
255
+ }
256
+ const otherAAR: string =
257
+ viewerGrid.dataFrame.get(aminoAcidResidue, viewerGrid.dataFrame.currentRowIdx);
258
+ let otherRowIndex = -1;
259
+ for (let i = 0; i < viewerVGrid.dataFrame.rowCount; i++) {
260
+ if (
261
+ viewerVGrid.dataFrame.get(aminoAcidResidue, i) === otherAAR &&
262
+ viewerVGrid.dataFrame.get('Position', i) === otherPos
263
+ ) {
264
+ otherRowIndex = i;
265
+ break;
266
+ }
267
+ }
268
+ if (otherRowIndex !== -1) {
269
+ viewerVGrid.dataFrame.currentCell = viewerVGrid.dataFrame.cell(otherRowIndex, 'Mean difference');
270
+ }
271
+ }
272
+ }
273
+ }
274
+
275
+ function sourceFilteringFunc(
276
+ filterMode: boolean,
277
+ dataFrame: DG.DataFrame,
278
+ currentBitset: DG.BitSet,
279
+ initialBitset: DG.BitSet,
280
+ ) {
281
+ if (filterMode) {
282
+ dataFrame.selection.setAll(false, false);
283
+ dataFrame.filter.copyFrom(currentBitset);
284
+ } else {
285
+ dataFrame.filter.copyFrom(initialBitset);
286
+ dataFrame.selection.copyFrom(currentBitset);
287
+ }
288
+ }
289
+
290
+ function applyBitset(
291
+ dataFrame: DG.DataFrame,
292
+ viewerGrid: DG.Grid,
293
+ aminoAcidResidue: string,
294
+ groupMapping: {[key: string]: string},
295
+ initialBitset: DG.BitSet,
296
+ filterMode: boolean,
297
+ ) {
298
+ let currentBitset = null;
299
+ if (
300
+ viewerGrid.dataFrame &&
301
+ viewerGrid.dataFrame.currentCell.value &&
302
+ viewerGrid.dataFrame.currentCol.name !== aminoAcidResidue
303
+ ) {
304
+ const currentAAR: string =
305
+ viewerGrid.dataFrame.get(aminoAcidResidue, viewerGrid.dataFrame.currentRowIdx);
306
+ const currentPosition = viewerGrid.dataFrame.currentCol.name;
307
+
308
+ const splitColName = '~splitCol';
309
+ const otherLabel = 'Other';
310
+ const aarLabel = `${currentAAR === '-' ? 'Empty' : currentAAR} - ${currentPosition}`;
311
+
312
+ let splitCol = dataFrame.col(splitColName);
313
+ if (!splitCol) {
314
+ splitCol = dataFrame.columns.addNew(splitColName, 'string');
315
+ }
316
+
317
+ const isChosen = (i: number) => groupMapping[dataFrame!.get(currentPosition, i)] === currentAAR;
318
+ splitCol!.init((i) => isChosen(i) ? aarLabel : otherLabel);
319
+
320
+ //TODO: use column.compact
321
+ currentBitset = DG.BitSet.create(dataFrame.rowCount, isChosen).and(initialBitset);
322
+ sourceFilteringFunc(filterMode, dataFrame, currentBitset, initialBitset);
323
+
324
+ const colorMap: {[index: string]: string | number} = {};
325
+ colorMap[otherLabel] = DG.Color.blue;
326
+ colorMap[aarLabel] = DG.Color.orange;
327
+ // colorMap[currentAAR] = cp.getColor(currentAAR);
328
+ dataFrame.getCol(splitColName).colors.setCategorical(colorMap);
329
+ }
330
+ return currentBitset;
331
+ }
332
+
333
+ function accordionFunc(
334
+ accordion: DG.Accordion,
335
+ viewerGrid: DG.Grid,
336
+ aminoAcidResidue: string,
337
+ initialBitset: DG.BitSet,
338
+ activityColumnName: string,
339
+ statsDf: DG.DataFrame,
340
+ ) {
341
+ if (accordion.context instanceof DG.RowGroup) {
342
+ const originalDf: DG.DataFrame = DG.toJs(accordion.context.dataFrame);
343
+ const viewerDf = viewerGrid.dataFrame;
344
+
345
+ if (
346
+ originalDf.getTag('dataType') === 'peptides' &&
347
+ originalDf.col('~splitCol') &&
348
+ viewerDf &&
349
+ viewerDf.currentCol !== null
350
+ ) {
351
+ const currentAAR: string = viewerDf.get(
352
+ aminoAcidResidue,
353
+ viewerDf.currentRowIdx,
354
+ );
355
+ const currentPosition = viewerDf.currentCol.name;
356
+
357
+ const labelStr = `${currentAAR === '-' ? 'Empty' : currentAAR} - ${currentPosition}`;
358
+ const currentColor = DG.Color.toHtml(DG.Color.orange);
359
+ const otherColor = DG.Color.toHtml(DG.Color.blue);
360
+ const currentLabel = ui.label(labelStr, {style: {color: currentColor}});
361
+ const otherLabel = ui.label('Other', {style: {color: otherColor}});
362
+
363
+ const elements: (HTMLLabelElement | HTMLElement)[] = [currentLabel, otherLabel];
364
+
365
+ const distPane = accordion.getPane('Distribution');
366
+ if (distPane) {
367
+ accordion.removePane(distPane);
368
+ }
369
+ accordion.addPane('Distribution', () => {
370
+ const hist = originalDf.clone(initialBitset).plot.histogram({
371
+ // const hist = originalDf.plot.histogram({
372
+ filteringEnabled: false,
373
+ valueColumnName: `${activityColumnName}Scaled`,
374
+ splitColumnName: '~splitCol',
375
+ legendVisibility: 'Never',
376
+ showXAxis: true,
377
+ showColumnSelector: false,
378
+ showRangeSlider: false,
379
+ }).root;
380
+ hist.style.width = 'auto';
381
+ elements.push(hist);
382
+
383
+ const tableMap: {[key: string]: string} = {'Statistics:': ''};
384
+ for (const colName of new Set(['Count', 'pValue', 'Mean difference'])) {
385
+ const query = `${aminoAcidResidue} = ${currentAAR} and Position = ${currentPosition}`;
386
+ const textNum = statsDf.groupBy([colName]).where(query).aggregate().get(colName, 0);
387
+ // const text = textNum === 0 ? '<0.01' : `${colName === 'Count' ? textNum : textNum.toFixed(2)}`;
388
+ const text = colName === 'Count' ? `${textNum}` : textNum < 0.01 ? '<0.01' : textNum.toFixed(2);
389
+ tableMap[colName === 'pValue' ? 'p-value' : colName] = text;
390
+ }
391
+ elements.push(ui.tableFromMap(tableMap));
392
+
393
+ return ui.divV(elements);
394
+ }, true);
395
+ }
396
+ }
397
+ }
@@ -42,7 +42,9 @@ export function addViewerToHeader(grid: DG.Grid, viewer: Promise<DG.Widget>) {
42
42
  return true;
43
43
  } else {
44
44
  if (barchart.highlighted) {
45
- ui.tooltip.show(ui.divV([ui.divText(barchart.highlighted.aaName)]), x, y);
45
+ let elements: HTMLElement[] = [];
46
+ elements = elements.concat([ui.divText(barchart.highlighted.aaName)]);
47
+ ui.tooltip.show(ui.divV(elements), x, y);
46
48
  }
47
49
  return true;
48
50
  }
@@ -54,6 +56,7 @@ export function addViewerToHeader(grid: DG.Grid, viewer: Promise<DG.Widget>) {
54
56
  args.g.beginPath();
55
57
  args.g.rect(args.bounds.x, args.bounds.y, args.bounds.width, args.bounds.height);
56
58
  args.g.clip();
59
+
57
60
  if (args.cell.isColHeader && barchart.aminoColumnNames.includes(args.cell.gridColumn.name)) {
58
61
  barchart.renderBarToCanvas(
59
62
  args.g,
@@ -251,7 +254,14 @@ export class StackedBarChart extends DG.JsViewer {
251
254
  this.max = df.filter.trueCount;
252
255
  }
253
256
 
254
- renderBarToCanvas(g: CanvasRenderingContext2D, cell: DG.GridCell, x: number, y: number, w: number, h: number) {
257
+ renderBarToCanvas(
258
+ g: CanvasRenderingContext2D,
259
+ cell: DG.GridCell,
260
+ x: number,
261
+ y: number,
262
+ w: number,
263
+ h: number,
264
+ ) {
255
265
  const margin = 0.2;
256
266
  const innerMargin = 0.02;
257
267
  const selectLineRatio = 0.1;