@datagrok/peptides 0.0.1 → 0.2.0

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.
@@ -0,0 +1,242 @@
1
+ /* Do not change these import lines. Datagrok will import API library in exactly the same manner */
2
+ // eslint-disable-next-line no-unused-vars
3
+ import * as grok from 'datagrok-api/grok';
4
+ import * as ui from 'datagrok-api/ui';
5
+ import * as DG from 'datagrok-api/dg';
6
+
7
+ import {getSequenceMolecularWeight} from './molecular-measure';
8
+ import {AlignedSequenceEncoder} from '@datagrok-libraries/utils/src/sequence-encoder';
9
+ import {DimensionalityReducer} from '@datagrok-libraries/utils/src/reduce-dimensionality';
10
+ import {Measurer} from '@datagrok-libraries/utils/src/string-measure';
11
+ import {Coordinates} from '@datagrok-libraries/utils/src/type_declarations';
12
+
13
+ /**
14
+ * Creates a worker to perform a dimensionality reduction.
15
+ *
16
+ * @param {any[]} columnData Samples to process.
17
+ * @param {string} method Embedding method.
18
+ * @param {string} measure Distance metric.
19
+ * @param {number} cyclesCount Number of cycles to repeat.
20
+ * @return {Promise<unknown>} Promise.
21
+ */
22
+ function createDimensinalityReducingWorker(
23
+ columnData: any[],
24
+ method: string,
25
+ measure: string,
26
+ cyclesCount: number,
27
+ ): Promise<unknown> {
28
+ return new Promise(function(resolve) {
29
+ const worker = new Worker(new URL('../workers/dimensionality-reducer.ts', import.meta.url));
30
+ worker.postMessage({
31
+ columnData: columnData,
32
+ method: method,
33
+ measure: measure,
34
+ cyclesCount: cyclesCount,
35
+ });
36
+ worker.onmessage = ({data: {embedding}}) => {
37
+ resolve(embedding);
38
+ };
39
+ });
40
+ }
41
+
42
+ /**
43
+ * Finds a column with an activity.
44
+ *
45
+ * @param {DG.DataFrame} table The data frame to search for.
46
+ * @return {(string | null)} Column name or null if not found.
47
+ */
48
+ function inferActivityColumnsName(table: DG.DataFrame): string | null {
49
+ const re = /activity|ic50/i;
50
+ for (const name of table.columns.names()) {
51
+ if (name.match(re)) {
52
+ console.log(`${name} found.`);
53
+ return name;
54
+ }
55
+ }
56
+ return null;
57
+ }
58
+
59
+ /**
60
+ * Creates scatter plot with sequences embeded.
61
+ *
62
+ * @export
63
+ * @param {DG.DataFrame} table The table containing samples.
64
+ * @param {DG.Column} alignedSequencesColumn Samples column.
65
+ * @param {string} method Embedding method to apply.
66
+ * @param {string} measure Distance metric.
67
+ * @param {number} cyclesCount Number of cycles to repeat.
68
+ * @param {(string | null)} [activityColumnName] Activity containing column to assign it to points radius.
69
+ * @param {boolean} [zoom=false] Whether to fit view.
70
+ * @return {Promise<DG.ScatterPlotViewer>} A viewer.
71
+ */
72
+ export async function createPeptideSimilaritySpaceViewer(
73
+ table: DG.DataFrame,
74
+ alignedSequencesColumn: DG.Column,
75
+ method: string,
76
+ measure: string,
77
+ cyclesCount: number,
78
+ activityColumnName?: string | null,
79
+ zoom: boolean = false,
80
+ ): Promise<DG.ScatterPlotViewer> {
81
+ const pi = DG.TaskBarProgressIndicator.create('Creating embedding.');
82
+
83
+ activityColumnName = activityColumnName ?? inferActivityColumnsName(table);
84
+
85
+ const axesNames = ['~X', '~Y', '~MW'];
86
+ const columnData = alignedSequencesColumn.toList().map((v, _) => AlignedSequenceEncoder.clean(v));
87
+
88
+ const embcols = await createDimensinalityReducingWorker(columnData, method, measure, cyclesCount);
89
+
90
+ const columns = Array.from(
91
+ embcols as Coordinates,
92
+ (v: Float32Array, k) => (DG.Column.fromFloat32Array(axesNames[k], v)),
93
+ );
94
+
95
+ function _getMW(sequences = columnData) {
96
+ const mw: Float32Array = new Float32Array(sequences.length).fill(0);
97
+ let currentSequence;
98
+
99
+ for (let i = 0; i < sequences.length; ++i) {
100
+ currentSequence = sequences[i];
101
+ mw[i] = currentSequence == null ? 0 : getSequenceMolecularWeight(currentSequence);
102
+ }
103
+ return mw;
104
+ }
105
+
106
+ columns.push(DG.Column.fromFloat32Array('~MW', _getMW()));
107
+
108
+ const edf = DG.DataFrame.fromColumns(columns);
109
+
110
+ // Add new axes.
111
+ for (const axis of axesNames) {
112
+ const col = table.col(axis);
113
+
114
+ if (col == null) {
115
+ table.columns.insert(edf.getCol(axis));
116
+ } else {
117
+ table.columns.replace(col, edf.getCol(axis));
118
+ }
119
+ }
120
+ const viewer = DG.Viewer.scatterPlot(table, {x: '~X', y: '~Y', color: activityColumnName ?? '~MW', size: '~MW'});
121
+
122
+ // Fit view if needed.
123
+ /*if (zoom) {
124
+ viewer.zoom(
125
+ table.getCol('~X').min,
126
+ table.getCol('~Y').min,
127
+ table.getCol('~X').max,
128
+ table.getCol('~Y').max,
129
+ );
130
+ }*/
131
+ pi.close();
132
+ return viewer;
133
+ }
134
+
135
+ /**
136
+ * Controls creation of the peptide similarity space viewer.
137
+ *
138
+ * @export
139
+ * @class PeptideSimilaritySpaceWidget
140
+ */
141
+ export class PeptideSimilaritySpaceWidget {
142
+ protected method: string;
143
+ protected metrics: string;
144
+ protected cycles: number = 100;
145
+ protected currentDf: DG.DataFrame;
146
+ protected alignedSequencesColumn: DG.Column;
147
+ protected availableMethods: string[];
148
+ protected availableMetrics: string[];
149
+ protected viewer: HTMLElement;
150
+
151
+ /**
152
+ * Creates an instance of PeptideSimilaritySpaceWidget.
153
+ * @param {DG.Column} alignedSequencesColumn The column to get amino acid sequences from.
154
+ * @memberof PeptideSimilaritySpaceWidget
155
+ */
156
+ constructor(alignedSequencesColumn: DG.Column) {
157
+ this.availableMethods = DimensionalityReducer.availableMethods;
158
+ this.availableMetrics = Measurer.availableMeasures;
159
+ this.method = this.availableMethods[0];
160
+ this.metrics = this.availableMetrics[0];
161
+ this.currentDf = alignedSequencesColumn.dataFrame;
162
+ this.alignedSequencesColumn = alignedSequencesColumn;
163
+ this.viewer = ui.div([]);
164
+ }
165
+
166
+ /**
167
+ * Creates viewer itself.
168
+ *
169
+ * @return {Promise<DG.Viewer>} the viewer.
170
+ * @memberof PeptideSimilaritySpaceWidget
171
+ */
172
+ public async drawViewer(): Promise<DG.Viewer> {
173
+ const viewer = await createPeptideSimilaritySpaceViewer(
174
+ this.currentDf,
175
+ this.alignedSequencesColumn,
176
+ this.method,
177
+ this.metrics,
178
+ this.cycles,
179
+ null,
180
+ true,
181
+ );
182
+ viewer.root.style.width = 'auto';
183
+ return viewer;
184
+ }
185
+
186
+ /**
187
+ * Updates the viewer on options changes.
188
+ *
189
+ * @protected
190
+ * @memberof PeptideSimilaritySpaceWidget
191
+ */
192
+ protected async updateViewer() {
193
+ this.viewer.lastChild?.remove();
194
+ const viewer = await this.drawViewer();
195
+ this.viewer.appendChild(viewer.root);
196
+ }
197
+
198
+ /**
199
+ * Adds input controls to manage the viewer's parameters.
200
+ *
201
+ * @protected
202
+ * @return {Promise<HTMLElement>} Bunch of control elements.
203
+ * @memberof PeptideSimilaritySpaceWidget
204
+ */
205
+ protected async drawInputs(): Promise<HTMLElement> {
206
+ const methodsList = ui.choiceInput('Embedding method', this.method, this.availableMethods,
207
+ async (currentMethod: string) => {
208
+ this.method = currentMethod;
209
+ await this.updateViewer();
210
+ },
211
+ );
212
+ methodsList.setTooltip('Embedding method to apply to the dataset.');
213
+
214
+ const metricsList = ui.choiceInput('Distance metric', this.metrics, this.availableMetrics,
215
+ async (currentMetrics: string) => {
216
+ this.metrics = currentMetrics;
217
+ await this.updateViewer();
218
+ },
219
+ );
220
+ metricsList.setTooltip('Custom distance metric to pass to the embedding procedure.');
221
+
222
+ const cyclesSlider = ui.intInput('Cycles count', this.cycles,
223
+ async (currentCycles: number) => {
224
+ this.cycles = currentCycles;
225
+ await this.updateViewer();
226
+ },
227
+ );
228
+ cyclesSlider.setTooltip('Number of cycles affects the embedding quality.');
229
+
230
+ return ui.inputs([methodsList, metricsList, cyclesSlider]);
231
+ }
232
+
233
+ /**
234
+ * Draws a viewer on property panel.
235
+ *
236
+ * @return {Promise<DG.Widget>} The corresponding widget.
237
+ * @memberof PeptideSimilaritySpaceWidget
238
+ */
239
+ public async draw(): Promise<DG.Widget> {
240
+ return new DG.Widget(ui.divV([(await this.drawViewer()).root, await this.drawInputs()]));
241
+ }
242
+ }
@@ -0,0 +1,65 @@
1
+ import * as DG from 'datagrok-api/dg';
2
+
3
+ export function splitAlignedPeptides(peptideColumn: DG.Column, filter: boolean = true): [DG.DataFrame, number[]] {
4
+ const splitPeptidesArray: string[][] = [];
5
+ let currentSplitPeptide: string[];
6
+ let modeMonomerCount = 0;
7
+ let currentLength;
8
+ const colLength = peptideColumn.length;
9
+
10
+ // splitting data
11
+ const monomerLengths: {[index: string]: number} = {};
12
+ for (let i = 0; i < colLength; i++) {
13
+ currentSplitPeptide = peptideColumn.get(i).split('-').map((value: string) => value ? value : '-');
14
+ splitPeptidesArray.push(currentSplitPeptide);
15
+ currentLength = currentSplitPeptide.length;
16
+ monomerLengths[currentLength + ''] =
17
+ monomerLengths[currentLength + ''] ? monomerLengths[currentLength + ''] + 1 : 1;
18
+ }
19
+ //@ts-ignore: what I do here is converting string to number the most effective way I could find. parseInt is slow
20
+ modeMonomerCount = 1 * Object.keys(monomerLengths).reduce((a, b) => monomerLengths[a] > monomerLengths[b] ? a : b);
21
+
22
+ // making sure all of the sequences are of the same size
23
+ // and marking invalid sequences
24
+ let nTerminal: string;
25
+ const invalidIndexes: number[] = [];
26
+ let splitColumns: string[][] = Array.from({length: modeMonomerCount}, (_) => []);
27
+ modeMonomerCount--; // minus N-terminal
28
+ for (let i = 0; i < colLength; i++) {
29
+ currentSplitPeptide = splitPeptidesArray[i];
30
+ nTerminal = currentSplitPeptide.pop()!; // it is guaranteed that there will be at least one element
31
+ currentLength = currentSplitPeptide.length;
32
+ if (currentLength !== modeMonomerCount) {
33
+ invalidIndexes.push(i);
34
+ }
35
+ for (let j = 0; j < modeMonomerCount; j++) {
36
+ splitColumns[j].push(j < currentLength ? currentSplitPeptide[j] : '-');
37
+ }
38
+ splitColumns[modeMonomerCount].push(nTerminal);
39
+ }
40
+ modeMonomerCount--; // minus C-terminal
41
+
42
+ //create column names list
43
+ const columnNames = Array.from({length: modeMonomerCount}, (_, index) => `${index + 1 < 10 ? 0 : ''}${index + 1 }`);
44
+ columnNames.splice(0, 0, 'N-terminal');
45
+ columnNames.push('C-terminal');
46
+
47
+ // filter out the columns with the same values
48
+
49
+ if (filter) {
50
+ splitColumns = splitColumns.filter((positionArray, index) => {
51
+ const isRetained = new Set(positionArray).size > 1;
52
+ if (!isRetained) {
53
+ columnNames.splice(index, 1);
54
+ }
55
+ return isRetained;
56
+ });
57
+ }
58
+
59
+ return [
60
+ DG.DataFrame.fromColumns(splitColumns.map((positionArray, index) => {
61
+ return DG.Column.fromList('string', columnNames[index], positionArray);
62
+ })),
63
+ invalidIndexes,
64
+ ];
65
+ }
@@ -4,7 +4,7 @@ import * as DG from 'datagrok-api/dg';
4
4
  import $ from 'cash-dom';
5
5
 
6
6
  import * as logojs from 'logojs-react';
7
- import {splitAlignedPeptides} from '../split-aligned';
7
+ import {splitAlignedPeptides} from '../utils/split-aligned';
8
8
  import {ChemPalette} from '../utils/chem-palette';
9
9
 
10
10
  export class Logo extends DG.JsViewer {
@@ -31,6 +31,7 @@ export class Logo extends DG.JsViewer {
31
31
  'A': 0, 'B': 1, 'C': 2, 'D': 3, 'E': 4, 'F': 5, 'G': 6, 'H': 7, 'I': 8, 'K': 9, 'L': 10, 'M': 11,
32
32
  'N': 12, 'P': 13, 'Q': 14, 'R': 15, 'S': 16, 'T': 17, 'U': 18, 'V': 19, 'W': 20, 'Y': 21, 'Z': 22,
33
33
  };
34
+ //TODO: use chem palette
34
35
  this.LET_COLORS = [
35
36
  {color: 'rgb(44,160,44)', regex: 'A'},
36
37
  {color: 'rgb(44,160,44)', regex: 'B'},
@@ -63,9 +64,10 @@ export class Logo extends DG.JsViewer {
63
64
  // this.reactHost = ui.div([]);
64
65
  console.log('INIT');
65
66
  this.target = this.dataFrame;
66
- this.splitted = splitAlignedPeptides(this.dataFrame!.columns.bySemType(this.colSemType));
67
+ [this.splitted] = splitAlignedPeptides(this.dataFrame!.columns.bySemType(this.colSemType));
67
68
  this.root.style.width = 'auto';
68
69
  this.root.style.height = 'auto';
70
+ this.root.style.maxHeight = '200px';
69
71
  }
70
72
 
71
73
  onTableAttached() {
@@ -103,8 +105,8 @@ export class Logo extends DG.JsViewer {
103
105
  .aggregate();
104
106
  }
105
107
  if (selected) {
106
- this.splitted = splitAlignedPeptides(this.target!.columns.bySemType(this.colSemType));
107
- } else this.splitted = splitAlignedPeptides(this.dataFrame!.columns.bySemType(this.colSemType));
108
+ [this.splitted] = splitAlignedPeptides(this.target!.columns.bySemType(this.colSemType));
109
+ } else [this.splitted] = splitAlignedPeptides(this.dataFrame!.columns.bySemType(this.colSemType));
108
110
  $(this.root).empty();
109
111
 
110
112
  if (typeof this.dataFrame !== 'undefined') {
@@ -0,0 +1,63 @@
1
+ import * as DG from 'datagrok-api/dg';
2
+
3
+ import {describe} from '../describe';
4
+ import { Subject } from 'rxjs';
5
+
6
+ class SARViewerModel {
7
+ private viewerGrid: Subject<DG.Grid> = new Subject<DG.Grid>();
8
+ private viewerVGrid: Subject<DG.Grid> = new Subject<DG.Grid>();
9
+ private statsDf: Subject<DG.DataFrame> = new Subject<DG.DataFrame>();
10
+ public viewerGrid$ = this.viewerGrid.asObservable();
11
+ public viewerVGrid$ = this.viewerVGrid.asObservable();
12
+ public statsDf$ = this.statsDf.asObservable();
13
+ private dataFrame: DG.DataFrame | null;
14
+ private activityColumn: string | null;
15
+ private activityScaling: string | null;
16
+ private sourceGrid: DG.Grid | null;
17
+ private twoColorMode: boolean | null;
18
+ private initialBitset: DG.BitSet | null;
19
+ private isUpdating = false;
20
+
21
+ constructor() {
22
+ this.dataFrame = null;
23
+ this.activityColumn = null;
24
+ this.activityScaling = null;
25
+ this.sourceGrid = null;
26
+ this.twoColorMode = null;
27
+ this.initialBitset = null;
28
+ }
29
+
30
+ async updateData(
31
+ df: DG.DataFrame,
32
+ activityCol: string,
33
+ activityScaling: string,
34
+ sourceGrid: DG.Grid,
35
+ twoColorMode: boolean,
36
+ initialBitset: DG.BitSet | null,
37
+ ) {
38
+ this.dataFrame = df;
39
+ this.activityColumn = activityCol;
40
+ this.activityScaling = activityScaling;
41
+ this.sourceGrid = sourceGrid;
42
+ this.twoColorMode = twoColorMode;
43
+ this.initialBitset = initialBitset;
44
+ await this.updateDefault();
45
+ }
46
+
47
+ async updateDefault() {
48
+ if (this.dataFrame && this.activityColumn && this.activityScaling && this.sourceGrid && this.twoColorMode !== null && !this.isUpdating) {
49
+ this.isUpdating = true;
50
+ const [viewerGrid, viewerVGrid, statsDf] = await describe(
51
+ this.dataFrame, this.activityColumn, this.activityScaling,
52
+ this.sourceGrid, this.twoColorMode, this.initialBitset
53
+ );
54
+ this.viewerGrid.next(viewerGrid);
55
+ this.viewerVGrid.next(viewerVGrid);
56
+ this.statsDf.next(statsDf);
57
+ this.isUpdating = false;
58
+ }
59
+ }
60
+ }
61
+
62
+ export let model = new SARViewerModel();
63
+
@@ -4,12 +4,11 @@ import * as DG from 'datagrok-api/dg';
4
4
 
5
5
  import $ from 'cash-dom';
6
6
 
7
- import {describe} from './describe';
7
+ import { model } from './model';
8
8
 
9
- export class SARViewerBase extends DG.JsViewer {
9
+ export class SARViewer extends DG.JsViewer {
10
10
  protected viewerGrid: DG.Grid | null;
11
11
  protected sourceGrid: DG.Grid | null;
12
- protected progress: DG.TaskBarProgressIndicator;
13
12
  protected activityColumnColumnName: string;
14
13
  protected activityScalingMethod: string;
15
14
  protected bidirectionalAnalysis: boolean;
@@ -21,13 +20,13 @@ export class SARViewerBase extends DG.JsViewer {
21
20
  protected _initialBitset: DG.BitSet | null;
22
21
  protected viewerVGrid: DG.Grid | null;
23
22
  protected currentBitset: DG.BitSet | null;
23
+ // private df: DG.DataFrame | null;
24
24
  // protected pValueThreshold: number;
25
25
  // protected amountOfBestAARs: number;
26
26
  // duplicatesHandingMethod: string;
27
27
  constructor() {
28
28
  super();
29
- this.progress = DG.TaskBarProgressIndicator.create('Loading SAR viewer');
30
-
29
+
31
30
  this.viewerGrid = null;
32
31
  this.viewerVGrid = null;
33
32
  this.statsDf = null;
@@ -52,10 +51,17 @@ export class SARViewerBase extends DG.JsViewer {
52
51
  init() {
53
52
  this._initialBitset = this.dataFrame!.filter.clone();
54
53
  this.initialized = true;
54
+ this.subs.push(model.statsDf$.subscribe(data => this.statsDf = data));
55
+ this.subs.push(model.viewerGrid$.subscribe(data => {
56
+ this.viewerGrid = data;
57
+ this.render();
58
+ }));
59
+ this.subs.push(model.viewerVGrid$.subscribe(data => this.viewerVGrid = data));
55
60
  }
56
61
 
57
62
  onTableAttached() {
58
63
  this.sourceGrid = this.view.grid;
64
+ this.sourceGrid?.dataFrame?.setTag('dataType', 'peptides');
59
65
  this.render();
60
66
  }
61
67
 
@@ -73,7 +79,7 @@ export class SARViewerBase extends DG.JsViewer {
73
79
 
74
80
  if (property.name === 'activityScalingMethod' && typeof this.dataFrame !== 'undefined') {
75
81
  const minActivity = DG.Stats.fromColumn(
76
- this.dataFrame.col(this.activityColumnColumnName)!,
82
+ this.dataFrame!.col(this.activityColumnColumnName)!,
77
83
  this._initialBitset,
78
84
  ).min;
79
85
  if (minActivity && minActivity <= 0 && this.activityScalingMethod !== 'none') {
@@ -103,12 +109,15 @@ export class SARViewerBase extends DG.JsViewer {
103
109
  const otherLabel = 'Other';
104
110
  const aarLabel = `${currentAAR === '-' ? 'Empty' : currentAAR} - ${currentPosition}`;
105
111
 
106
- if (!this.dataFrame.col(splitColName)) {
107
- this.dataFrame.columns.addNew(splitColName, 'string');
112
+ let splitCol = this.dataFrame.col(splitColName);
113
+ if (!splitCol) {
114
+ splitCol = this.dataFrame.columns.addNew(splitColName, 'string');
108
115
  }
109
116
 
110
117
  const isChosen = (i: number) => this.dataFrame!.get(currentPosition, i) === currentAAR;
111
- this.dataFrame.getCol(splitColName).init((i) => isChosen(i) ? aarLabel : otherLabel);
118
+ splitCol!.init((i) => isChosen(i) ? aarLabel : otherLabel);
119
+
120
+ //TODO: use column.compact
112
121
 
113
122
  // if (this.filterMode) {
114
123
  // this.dataFrame.selection.setAll(false, false);
@@ -118,7 +127,8 @@ export class SARViewerBase extends DG.JsViewer {
118
127
  // this.dataFrame.selection.init(isChosen).and(this._initialBitset!, false);
119
128
  // }
120
129
  this.currentBitset = DG.BitSet.create(this.dataFrame.rowCount, isChosen).and(this._initialBitset!);
121
- // (this.filterMode ? this.dataFrame.selection.setAll(false) : this.dataFrame.filter.copyFrom(this._initialBitset!)).fireChanged();
130
+ // (this.filterMode ? this.dataFrame.selection.setAll(false) :
131
+ // this.dataFrame.filter.copyFrom(this._initialBitset!)).fireChanged();
122
132
  this.sourceFilteringFunc();
123
133
 
124
134
 
@@ -143,19 +153,21 @@ export class SARViewerBase extends DG.JsViewer {
143
153
 
144
154
  private accordionFunc(accordion: DG.Accordion) {
145
155
  if (accordion.context instanceof DG.RowGroup) {
146
- const originalDf: DG.DataFrame = accordion.context.dataFrame;
156
+ const originalDf: DG.DataFrame = DG.toJs(accordion.context.dataFrame);
157
+ const viewerDf = this.viewerGrid?.dataFrame;
147
158
 
148
159
  if (
149
160
  originalDf.getTag('dataType') === 'peptides' &&
150
161
  originalDf.col('~splitCol') &&
151
162
  this.viewerGrid &&
152
- this.viewerGrid.dataFrame
163
+ viewerDf &&
164
+ viewerDf.currentCol !== null
153
165
  ) {
154
- const currentAAR: string = this.viewerGrid.dataFrame.get(
166
+ const currentAAR: string = viewerDf.get(
155
167
  this.aminoAcidResidue,
156
- this.viewerGrid.dataFrame.currentRowIdx,
168
+ viewerDf.currentRowIdx,
157
169
  );
158
- const currentPosition = this.viewerGrid.dataFrame.currentCol.name;
170
+ const currentPosition = viewerDf.currentCol.name;
159
171
 
160
172
  const labelStr = `${currentAAR === '-' ? 'Empty' : currentAAR} - ${currentPosition}`;
161
173
  const currentColor = DG.Color.toHtml(DG.Color.orange);
@@ -187,7 +199,8 @@ export class SARViewerBase extends DG.JsViewer {
187
199
  for (const colName of new Set(['Count', 'pValue', 'Mean difference'])) {
188
200
  const query = `${this.aminoAcidResidue} = ${currentAAR} and Position = ${currentPosition}`;
189
201
  const textNum = this.statsDf?.groupBy([colName]).where(query).aggregate().get(colName, 0);
190
- const text = textNum === 0 ? '<0.01' : `${colName === 'Count' ? textNum : textNum.toFixed(2)}`;
202
+ // const text = textNum === 0 ? '<0.01' : `${colName === 'Count' ? textNum : textNum.toFixed(2)}`;
203
+ const text = colName === 'Count' ? `${textNum}` : textNum < 0.01 ? '<0.01' : textNum.toFixed(2);
191
204
  tableMap[colName === 'pValue' ? 'p-value' : colName] = text;
192
205
  }
193
206
  elements.push(ui.tableFromMap(tableMap));
@@ -249,8 +262,16 @@ export class SARViewerBase extends DG.JsViewer {
249
262
  //TODO: optimize. Don't calculate everything again if only view changes
250
263
  if (computeData) {
251
264
  if (typeof this.dataFrame !== 'undefined' && this.activityColumnColumnName && this.sourceGrid) {
252
- [this.viewerGrid, this.viewerVGrid, this.statsDf] = await describe(
253
- this.dataFrame,
265
+ // [this.viewerGrid, this.viewerVGrid, this.statsDf] = await describe(
266
+ // this.dataFrame,
267
+ // this.activityColumnColumnName,
268
+ // this.activityScalingMethod,
269
+ // this.sourceGrid,
270
+ // this.bidirectionalAnalysis,
271
+ // this._initialBitset,
272
+ // );
273
+ await model?.updateData(
274
+ this.dataFrame!,
254
275
  this.activityColumnColumnName,
255
276
  this.activityScalingMethod,
256
277
  this.sourceGrid,
@@ -260,22 +281,39 @@ export class SARViewerBase extends DG.JsViewer {
260
281
 
261
282
  if (this.viewerGrid !== null && this.viewerVGrid !== null) {
262
283
  $(this.root).empty();
263
- this.root.appendChild(ui.splitV([this.viewerGrid.root, this.viewerVGrid.root]));
284
+ this.root.appendChild(this.viewerGrid.root);
264
285
  this.viewerGrid.dataFrame!.onCurrentCellChanged.subscribe((_) => {
265
286
  this.applyBitset();
266
287
  this.syncGridsFunc(false);
267
288
  });
268
289
  this.viewerVGrid.dataFrame!.onCurrentCellChanged.subscribe((_) => this.syncGridsFunc(true));
269
- this.dataFrame.onRowsFiltering.subscribe((_) => this.sourceFilteringFunc());
270
-
290
+ this.dataFrame!.onRowsFiltering.subscribe((_) => this.sourceFilteringFunc());
271
291
  grok.events.onAccordionConstructed.subscribe((accordion: DG.Accordion) => this.accordionFunc(accordion));
272
292
  }
273
293
  }
274
294
  }
275
295
  //fixes viewers not rendering immediately after analyze.
276
- this.viewerVGrid?.invalidate();
277
296
  this.viewerGrid?.invalidate();
278
-
279
- this.progress.close();
280
297
  }
281
298
  }
299
+
300
+ export class SARViewerVertical extends DG.JsViewer {
301
+ viewerVGrid: DG.Grid | null;
302
+ constructor() {
303
+ super();
304
+
305
+ this.viewerVGrid = null;
306
+ this.subs.push(model.viewerVGrid$.subscribe(data => {
307
+ this.viewerVGrid = data;
308
+ this.render();
309
+ }));
310
+ }
311
+
312
+ render() {
313
+ if (this.viewerVGrid) {
314
+ $(this.root).empty();
315
+ this.root.appendChild(this.viewerVGrid.root);
316
+ }
317
+ this.viewerVGrid?.invalidate();
318
+ }
319
+ }