@datagrok/peptides 1.17.20 → 1.17.21

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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@datagrok/peptides",
3
3
  "friendlyName": "Peptides",
4
- "version": "1.17.20",
4
+ "version": "1.17.21",
5
5
  "author": {
6
6
  "name": "Davit Rizhinashvili",
7
7
  "email": "drizhinashvili@datagrok.ai"
package/src/package.ts CHANGED
@@ -14,6 +14,7 @@ import {PeptidesModel} from './model';
14
14
  import {macromoleculeSarFastaDemoUI} from './demo/fasta';
15
15
  import {u2} from '@datagrok-libraries/utils/src/u2';
16
16
  import {ClusterMaxActivityViewer} from './viewers/cluster-max-activity-viewer';
17
+ import {LSTPieChartRenderer} from './utils/cell-renderer';
17
18
 
18
19
  let monomerWorks: MonomerWorks | null = null;
19
20
  let treeHelper: ITreeHelper;
@@ -169,3 +170,12 @@ export function manualAlignment(_monomer: string): DG.Widget {
169
170
  export async function macromoleculeSarFastaDemo(): Promise<void> {
170
171
  return macromoleculeSarFastaDemoUI();
171
172
  }
173
+
174
+ //name: LST Pie Chart
175
+ //tags: cellRenderer
176
+ //meta.cellType: lst-pie-chart
177
+ //meta.gridChart: true
178
+ //output: grid_cell_renderer result
179
+ export function lstPiechartCellRenderer(): LSTPieChartRenderer {
180
+ return new LSTPieChartRenderer();
181
+ }
@@ -1,3 +1,4 @@
1
+ /* eslint-disable max-len */
1
2
  import * as ui from 'datagrok-api/ui';
2
3
  import * as DG from 'datagrok-api/dg';
3
4
 
@@ -439,3 +440,190 @@ function findWebLogoMonomerPosition(cell: DG.GridCell, ev: MouseEvent, webLogoBo
439
440
 
440
441
  return null;
441
442
  }
443
+
444
+
445
+ /**
446
+ * Renderer for LST pie chart cell
447
+ *
448
+ */
449
+
450
+ export class LSTPieChartRenderer extends DG.GridCellRenderer {
451
+ get name(): string {return 'LST Pie Chart';}
452
+
453
+ get cellType(): string {return 'lst-pie-chart';}
454
+
455
+ get defaultWidth(): number | null {return 80;}
456
+
457
+ get defaultHeight(): number | null {return 80;}
458
+
459
+ colorCacheKey = 'lst-pie-chart-color-cache'; // key for temp storage of category number cache
460
+ categoryNumberCache(col: DG.Column): {[_: string]: number} {
461
+ if (!col.temp[this.colorCacheKey])
462
+ col.temp[this.colorCacheKey] = {};
463
+ return col.temp[this.colorCacheKey] as {[_: string]: number};
464
+ };
465
+
466
+ HoverTempKey = 'lst-pie-chart-hovered-sector'; // key for temp storage of hovered sector
467
+ hoverSeparator = ',,,,,'; // separator for hovered sector
468
+
469
+ getPrevHoveredSector(col: DG.Column): string | null {
470
+ return col.temp[this.HoverTempKey] as string;
471
+ }
472
+
473
+ onMouseMove(gridCell: DG.GridCell, e: MouseEvent): void {
474
+ const prev = this.getPrevHoveredSector(gridCell.cell.column);
475
+ const beforeReturn = (): void => {
476
+ gridCell.cell.column.temp[this.HoverTempKey] = null;
477
+ ui.tooltip.hide();
478
+
479
+ if (prev !== null)
480
+ gridCell.grid.invalidate();
481
+ };
482
+
483
+ const value = gridCell.cell.value;
484
+ if (!value) {
485
+ beforeReturn();
486
+ return;
487
+ }
488
+ const vectorX = e.offsetX - gridCell.bounds.midX;
489
+ const vectorY = e.offsetY - gridCell.bounds.midY;
490
+ const distance = Math.sqrt(vectorX * vectorX + vectorY * vectorY);
491
+ const atan2 = Math.atan2(vectorY, vectorX);
492
+ const angle = atan2 < 0 ? atan2 + 2 * Math.PI : atan2;
493
+ if (distance > Math.min(gridCell.bounds.width, gridCell.bounds.height) / 2 - 2) {
494
+ beforeReturn();
495
+ return;
496
+ }
497
+
498
+ let JSONData: {[_: string]: number};
499
+ try {
500
+ JSONData = JSON.parse(value);
501
+ } catch (e) {
502
+ beforeReturn();
503
+ return;
504
+ }
505
+
506
+ const angles = new Float64Array(Object.keys(JSONData).length + 1);
507
+ const totalCount = Object.values(JSONData).reduce((a, b) => a + b, 0);
508
+ angles[Object.keys(JSONData).length] = Math.PI * 2;
509
+ let angleAccum = 0;
510
+ let i = 0;
511
+ for (const val of Object.values(JSONData)) {
512
+ angles[i++] = angleAccum;
513
+ const angleAdd = 2 * Math.PI * val / totalCount;
514
+ angleAccum += angleAdd;
515
+ }
516
+
517
+ let hitCategory = -1;
518
+ for (let i = 0; i < angles.length - 1; i++) {
519
+ if (angle >= angles[i] && angle < angles[i + 1]) {
520
+ hitCategory = i;
521
+ break;
522
+ }
523
+ }
524
+ if (hitCategory === -1) {
525
+ beforeReturn();
526
+ return;
527
+ }
528
+ const hitCategoryName = Object.keys(JSONData)[hitCategory];
529
+ const hitCategoryValue = JSONData[hitCategoryName];
530
+ const percentage = (hitCategoryValue / totalCount * 100).toFixed(2);
531
+ ui.tooltip.show(ui.h1(`${hitCategoryName}: ${hitCategoryValue} (${percentage}%)`), e.x + 16, e.y +
532
+ 16);
533
+ const hoverTemp = gridCell.cell.rowIndex.toString() + this.hoverSeparator + hitCategoryName;
534
+ gridCell.cell.column.temp[this.HoverTempKey] = hoverTemp;
535
+ if (prev !== hoverTemp)
536
+ gridCell.grid.invalidate();
537
+ }
538
+
539
+ render(
540
+ g: CanvasRenderingContext2D,
541
+ x: number, y: number, w: number, h: number,
542
+ gridCell: DG.GridCell, _cellStyle: DG.GridCellStyle,
543
+ ): void {
544
+ const df = gridCell.cell.dataFrame;
545
+ const box = new DG.Rect(x, y, w, h).fitSquare().inflate(-2, -2);
546
+ if (w < 5 || h < 5 || !df) return;
547
+ const value = gridCell.cell.value;
548
+ g.clearRect(x, y, w, h);
549
+ if (!value) return;
550
+
551
+ let JSONData: {[_: string]: number};
552
+ try {
553
+ JSONData = JSON.parse(value);
554
+ } catch (e) {
555
+ return;
556
+ }
557
+
558
+ const totalCount = Object.values(JSONData).reduce((a, b) => a + b, 0);
559
+
560
+ let angleAccum = 0;
561
+
562
+ const alphas = Object.fromEntries(Object.keys(JSONData).map((key) => [key, 255]));
563
+
564
+ const hoverTemp = gridCell.cell.column.temp[this.HoverTempKey] as string;
565
+ if (hoverTemp && hoverTemp.split(this.hoverSeparator).length === 2) {
566
+ const [rowIdx, category] = hoverTemp.split(this.hoverSeparator);
567
+ if (rowIdx === gridCell.cell.rowIndex.toString()) {
568
+ for (const [key, _] of Object.entries(JSONData))
569
+ alphas[key] = 100;
570
+ alphas[category] = 255;
571
+ }
572
+ }
573
+
574
+ const radius = Math.min(w, h) / 2.2;
575
+
576
+ for (const [key, val] of Object.entries(JSONData)) {
577
+ const angle = 2 * Math.PI * val / totalCount;
578
+ g.beginPath();
579
+ g.moveTo(box.midX, box.midY);
580
+ g.arc(box.midX, box.midY, radius, angleAccum, angleAccum + angle);
581
+ angleAccum += angle;
582
+ const colorCache = this.categoryNumberCache(gridCell.cell.column);
583
+ if (!colorCache[key]) {
584
+ colorCache[key] = Object.keys(colorCache).length; // cache category number keys, so that colors are consistent
585
+ gridCell.cell.column.temp[this.colorCacheKey] = colorCache;
586
+ }
587
+ g.fillStyle = DG.Color.toHtml(
588
+ DG.Color.setAlpha(DG.Color.getCategoricalColor(colorCache[key]), alphas[key]),
589
+ );
590
+ g.fill();
591
+ g.strokeStyle = DG.Color.toRgb(DG.Color.lightGray);
592
+ g.stroke();
593
+
594
+
595
+ // text rendering
596
+ const text = key;
597
+ // g.font = '10px Arial'; // Adjust font size and family as needed
598
+ g.fillStyle = DG.Color.toRgb(DG.Color.white);
599
+ g.font = '9px Arial';
600
+ const textWidth = g.measureText(text).width;
601
+
602
+ const middleAngle = angleAccum - (angle / 2);
603
+
604
+ // get the best position for the text
605
+ for (let textRadiusMult = 0.8; textRadiusMult >= 0.4; textRadiusMult -= 0.05) {
606
+ const textRadius = radius * textRadiusMult;
607
+ const textX = box.midX + Math.cos(middleAngle) * textRadius;
608
+ const textY = box.midY + Math.sin(middleAngle) * textRadius;
609
+
610
+ // perform pseudo hit test to check if text fits in the pie slice
611
+ const leftPart = textX - textWidth / 2 - 2;
612
+ const rightPart = textX + textWidth / 2 + 2;
613
+
614
+ const isTextInside = [[leftPart, textY], [rightPart, textY]]
615
+ .every((point) => {
616
+ const d = Math.sqrt((point[0] - box.midX) ** 2 + (point[1] - box.midY) ** 2);
617
+ const at = Math.atan2(point[1] - box.midY, point[0] - box.midX);
618
+ const a = at < 0 ? at + 2 * Math.PI : at;
619
+ return d < radius - 2 && a > angleAccum - angle && a < angleAccum;
620
+ });
621
+
622
+ if (isTextInside) {
623
+ g.fillText(text, textX, textY);
624
+ break;
625
+ }
626
+ }
627
+ }
628
+ }
629
+ }
package/src/utils/misc.ts CHANGED
@@ -431,12 +431,14 @@ export function mutationCliffsToMaskInfo(mutationCliffs: type.MutationCliffs, ro
431
431
  * @param aggColsModel - Object with aggregation columns from analysis settings.
432
432
  * @return - Array of combined aggregation columns.
433
433
  */
434
- export function getTotalAggColumns(viewerSelectedColNames: string[], aggColsViewer: AggregationColumns,
435
- aggColsModel?: AggregationColumns): [string, DG.AggregationType][] {
434
+ export function getTotalAggColumns(df: DG.DataFrame, viewerSelectedColNames: string[],
435
+ aggColsViewer: AggregationColumns, aggColsModel?: AggregationColumns): [string, DG.AggregationType][] {
436
436
  const aggColsEntries = Object.entries(aggColsViewer);
437
437
  const aggColsEntriesFromSettings = !aggColsModel ? [] : Object.entries(aggColsModel)
438
438
  .filter((it) => !viewerSelectedColNames.includes(it[0]) || aggColsViewer[it[0]] !== it[1]);
439
- return aggColsEntries.concat(aggColsEntriesFromSettings);
439
+
440
+ return aggColsEntries.concat(aggColsEntriesFromSettings)
441
+ .filter((it) => df.columns.contains(it[0]) && df.col(it[0])!.matches('numerical'));
440
442
  }
441
443
 
442
444
  /**
@@ -152,3 +152,19 @@ export function getAggregatedColumnValuesFromDf(df: DG.DataFrame, idx: number,
152
152
  }
153
153
  return colResults;
154
154
  }
155
+
156
+ export function getStringColAggregatedJSON(df: DG.DataFrame, colName: string, mask?: DG.BitSet): string {
157
+ const col = df.col(colName.substring(5, colName.length - 1)); // remove 'dist(' and ')'
158
+ if (!col || !col.matches('categorical'))
159
+ return '{}';
160
+ mask ??= DG.BitSet.create(df.rowCount, () => true);
161
+ const values = col.getRawData();
162
+ const valueCounts = new Map<number, number>();
163
+ for (let i = -1; (i = mask.findNext(i, true)) !== -1;)
164
+ valueCounts.set(values[i], (valueCounts.get(values[i]) ?? 0) + 1);
165
+
166
+ const resJSON: {[_: string]: number} = {};
167
+ for (const [value, count] of valueCounts)
168
+ resJSON[col.categories[value]] = count;
169
+ return JSON.stringify(resJSON);
170
+ }
@@ -14,6 +14,7 @@ import {
14
14
  getAggregatedColumnValues,
15
15
  getAggregatedValue,
16
16
  getStats,
17
+ getStringColAggregatedJSON,
17
18
  StatsItem,
18
19
  } from '../utils/statistics';
19
20
  import wu from 'wu';
@@ -276,7 +277,9 @@ export class LogoSummaryTable extends DG.JsViewer implements ILogoSummaryTable {
276
277
  * @return - map of columns and aggregations.
277
278
  */
278
279
  getAggregationColumns(): AggregationColumns {
279
- return Object.fromEntries(this.columns.map((colName) => [colName, this.aggregation] as [string, DG.AGG]));
280
+ return Object.fromEntries(this.columns.map((colName) => [colName, this.aggregation] as [string, DG.AGG])
281
+ .filter(([colName, _]) => this.model.df.columns.contains(colName) &&
282
+ this.model.df.col(colName)!.matches('numerical')));
280
283
  }
281
284
 
282
285
  /** Processes attached table and sets viewer properties. */
@@ -411,7 +414,12 @@ export class LogoSummaryTable extends DG.JsViewer implements ILogoSummaryTable {
411
414
  */
412
415
  getTotalViewerAggColumns(): [string, DG.AggregationType][] {
413
416
  const aggrCols = this.getAggregationColumns();
414
- return getTotalAggColumns(this.columns, aggrCols, this.model?.settings?.columns);
417
+ return getTotalAggColumns(this.model.df, this.columns, aggrCols, this.model?.settings?.columns);
418
+ }
419
+
420
+ getStringAggregatedColumns(): string[] {
421
+ return this.columns.filter((colName) => this.model.df.columns.contains(colName) &&
422
+ this.model.df.col(colName)!.matches('categorical')).map((cn) => `dist(${cn})`);
415
423
  }
416
424
 
417
425
  /**
@@ -449,16 +457,18 @@ export class LogoSummaryTable extends DG.JsViewer implements ILogoSummaryTable {
449
457
  const customRatioColData = customLSTCols.addNewFloat(C.LST_COLUMN_NAMES.RATIO).getRawData();
450
458
 
451
459
  let origLSTBuilder = filteredDf.groupBy([clustersColName]);
452
- const aggColsEntries = this.getTotalViewerAggColumns();
453
- const aggColNames = aggColsEntries.map(([colName, aggFn]) => getAggregatedColName(aggFn, colName));
454
- const customAggRawCols = new Array(aggColNames.length);
455
- const colAggEntries = aggColsEntries.map(
460
+ const aggNumericColsEntries = this.getTotalViewerAggColumns();
461
+ const aggStringColNames = this.getStringAggregatedColumns();
462
+ const aggStringCols = aggStringColNames.map((colName) => customLSTCols.addNewString(colName));
463
+ const aggNumericColNames = aggNumericColsEntries.map(([colName, aggFn]) => getAggregatedColName(aggFn, colName));
464
+ const customAggRawCols = new Array(aggNumericColNames.length + aggStringColNames.length);
465
+ const numericColAggEntries = aggNumericColsEntries.map(
456
466
  ([colName, aggFn]) => [filteredDf.getCol(colName), aggFn] as [DG.Column<number>, DG.AggregationType]);
457
467
 
458
- for (let aggIdx = 0; aggIdx < aggColsEntries.length; ++aggIdx) {
459
- const [colName, aggFn] = aggColsEntries[aggIdx];
460
- origLSTBuilder = origLSTBuilder.add(aggFn, colName, aggColNames[aggIdx]);
461
- const customLSTAggCol = customLSTCols.addNewFloat(aggColNames[aggIdx]);
468
+ for (let aggIdx = 0; aggIdx < aggNumericColsEntries.length; ++aggIdx) {
469
+ const [colName, aggFn] = aggNumericColsEntries[aggIdx];
470
+ origLSTBuilder = origLSTBuilder.add(aggFn, colName, aggNumericColNames[aggIdx]);
471
+ const customLSTAggCol = customLSTCols.addNewFloat(aggNumericColNames[aggIdx]);
462
472
  customAggRawCols[aggIdx] = customLSTAggCol.getRawData();
463
473
  }
464
474
 
@@ -484,10 +494,15 @@ export class LogoSummaryTable extends DG.JsViewer implements ILogoSummaryTable {
484
494
  customPValColData[rowIdx] = stats.pValue ?? DG.FLOAT_NULL;
485
495
  customRatioColData[rowIdx] = stats.ratio;
486
496
 
487
- for (let aggColIdx = 0; aggColIdx < aggColNames.length; ++aggColIdx) {
488
- const [col, aggFn] = colAggEntries[aggColIdx];
497
+ for (let aggColIdx = 0; aggColIdx < aggNumericColNames.length; ++aggColIdx) {
498
+ const [col, aggFn] = numericColAggEntries[aggColIdx];
489
499
  customAggRawCols[aggColIdx][rowIdx] = getAggregatedValue(col, aggFn, bsMask);
490
500
  }
501
+ for (let aggColIdx = aggNumericColNames.length; aggColIdx < customAggRawCols.length; ++aggColIdx) {
502
+ const colName = aggStringColNames[aggColIdx - aggNumericColNames.length];
503
+ aggStringCols[aggColIdx - aggNumericColNames.length]
504
+ .set(rowIdx, getStringColAggregatedJSON(filteredDf, colName, bsMask));
505
+ }
491
506
  }
492
507
  customWebLogoCol.setTag(DG.TAGS.CELL_RENDERER, 'html');
493
508
  customDistCol.setTag(DG.TAGS.CELL_RENDERER, 'html');
@@ -512,6 +527,7 @@ export class LogoSummaryTable extends DG.JsViewer implements ILogoSummaryTable {
512
527
  const origMDColData = origLSTCols.addNewFloat(C.LST_COLUMN_NAMES.MEAN_DIFFERENCE).getRawData();
513
528
  const origPValColData = origLSTCols.addNewFloat(C.LST_COLUMN_NAMES.P_VALUE).getRawData();
514
529
  const origRatioColData = origLSTCols.addNewFloat(C.LST_COLUMN_NAMES.RATIO).getRawData();
530
+ const origAggStringCols = aggStringColNames.map((colName) => origLSTCols.addNewString(colName));
515
531
  const origBitsets: DG.BitSet[] = new Array(origLSTLen);
516
532
  const origClustMasks = Array.from({length: origLSTLen},
517
533
  () => new BitArray(filteredDfRowCount, false));
@@ -527,10 +543,15 @@ export class LogoSummaryTable extends DG.JsViewer implements ILogoSummaryTable {
527
543
  if (mask.allFalse)
528
544
  continue;
529
545
 
546
+
530
547
  const bsMask = DG.BitSet.fromBytes(mask.buffer.buffer, filteredDfRowCount);
531
548
  const stats = isDfFiltered ? getStats(activityColData, mask) :
532
549
  this.clusterStats[CLUSTER_TYPE.ORIGINAL][origLSTClustColCat[rowIdx]];
533
-
550
+ for (let aggColIdx = 0; aggColIdx < aggStringColNames.length; ++aggColIdx) {
551
+ const colName = aggStringColNames[aggColIdx];
552
+ origAggStringCols[aggColIdx]
553
+ .set(rowIdx, getStringColAggregatedJSON(filteredDf, colName, bsMask));
554
+ }
534
555
  origMembersColData[rowIdx] = stats.count;
535
556
  origBitsets[rowIdx] = bsMask;
536
557
  origMDColData[rowIdx] = stats.meanDifference;
@@ -544,6 +565,9 @@ export class LogoSummaryTable extends DG.JsViewer implements ILogoSummaryTable {
544
565
  // combine LSTs and create a grid
545
566
  const summaryTable = origLST.append(customLST);
546
567
  this.bitsets = origBitsets.concat(customBitsets);
568
+
569
+ aggStringColNames.forEach((sn) => summaryTable.col(sn)!.semType = 'lst-pie-chart');
570
+
547
571
  return summaryTable;
548
572
  }
549
573
 
@@ -563,7 +587,8 @@ export class LogoSummaryTable extends DG.JsViewer implements ILogoSummaryTable {
563
587
  const gridClustersCol = grid.col(C.LST_COLUMN_NAMES.CLUSTER)!;
564
588
  gridClustersCol.visible = true;
565
589
  grid.columns.setOrder([C.LST_COLUMN_NAMES.CLUSTER, C.LST_COLUMN_NAMES.MEMBERS,
566
- C.LST_COLUMN_NAMES.WEB_LOGO, C.LST_COLUMN_NAMES.DISTRIBUTION, C.LST_COLUMN_NAMES.MEAN_DIFFERENCE,
590
+ C.LST_COLUMN_NAMES.WEB_LOGO, ...this.getStringAggregatedColumns(),
591
+ C.LST_COLUMN_NAMES.DISTRIBUTION, C.LST_COLUMN_NAMES.MEAN_DIFFERENCE,
567
592
  C.LST_COLUMN_NAMES.P_VALUE, C.LST_COLUMN_NAMES.RATIO, ...aggColNames]);
568
593
  grid.columns.rowHeader!.visible = false;
569
594
  grid.props.rowHeight = 55;
@@ -800,13 +825,21 @@ export class LogoSummaryTable extends DG.JsViewer implements ILogoSummaryTable {
800
825
  const aggregatedValues: {
801
826
  [colName: string]: number
802
827
  } = {};
828
+ const stringAggregatedValues: {
829
+ [colName: string]: string
830
+ } = {};
803
831
  const aggColsEntries = this.getTotalViewerAggColumns();
832
+ const aggStringColNames = this.getStringAggregatedColumns();
804
833
  for (const [colName, aggFn] of aggColsEntries) {
805
834
  const newColName = getAggregatedColName(aggFn, colName);
806
835
  const col = this.dataFrame.getCol(colName);
807
836
  aggregatedValues[newColName] = getAggregatedValue(col, aggFn, currentSelection);
808
837
  }
809
838
 
839
+ for (const colName of aggStringColNames)
840
+ stringAggregatedValues[colName] = getStringColAggregatedJSON(this.dataFrame, colName, currentSelection);
841
+
842
+
810
843
  for (let i = 0; i < viewerDfColsLength; ++i) {
811
844
  const col = viewerDfCols.byIndex(i);
812
845
  newClusterVals[i] = col.name === C.LST_COLUMN_NAMES.CLUSTER ? newClusterName :
@@ -817,7 +850,8 @@ export class LogoSummaryTable extends DG.JsViewer implements ILogoSummaryTable {
817
850
  col.name === C.LST_COLUMN_NAMES.P_VALUE ? stats.pValue :
818
851
  col.name === C.LST_COLUMN_NAMES.RATIO ? stats.ratio :
819
852
  col.name in aggregatedValues ? aggregatedValues[col.name] :
820
- undefined;
853
+ col.name in stringAggregatedValues ? stringAggregatedValues[col.name] :
854
+ undefined;
821
855
  if (typeof newClusterVals[i] === 'undefined')
822
856
  _package.logger.warning(`PeptidesLSTWarn: value for column ${col.name} is undefined`);
823
857
  }
@@ -122,7 +122,6 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
122
122
  {category: PROPERTY_CATEGORIES.MUTATION_CLIFFS, min: 0, max: 100});
123
123
  this.maxMutations = this.int(SAR_PROPERTIES.MAX_MUTATIONS, 1,
124
124
  {category: PROPERTY_CATEGORIES.MUTATION_CLIFFS, min: 1, max: 20});
125
-
126
125
  this.columns = this.columnList(SAR_PROPERTIES.COLUMNS, [], {category: PROPERTY_CATEGORIES.AGGREGATION});
127
126
  this.aggregation = this.string(SAR_PROPERTIES.AGGREGATION, DG.AGG.AVG,
128
127
  {category: PROPERTY_CATEGORIES.AGGREGATION, choices: C.AGGREGATION_TYPES});
@@ -422,8 +421,9 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
422
421
  * @return - map of columns and aggregations.
423
422
  */
424
423
  getAggregationColumns(): AggregationColumns {
425
- return Object.fromEntries(
426
- this.columns.map((colName) => [colName, this.aggregation] as [string, DG.AGG]));
424
+ return Object.fromEntries(this.columns.map((colName) => [colName, this.aggregation] as [string, DG.AGG])
425
+ .filter(([colName, _]) => this.model.df.columns.contains(colName) &&
426
+ this.model.df.col(colName)!.matches('numerical')));
427
427
  }
428
428
 
429
429
  /**
@@ -432,7 +432,7 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
432
432
  */
433
433
  getTotalViewerAggColumns(): [string, DG.AggregationType][] {
434
434
  const aggrCols = this.getAggregationColumns();
435
- return getTotalAggColumns(this.columns, aggrCols, this.model?.settings?.columns);
435
+ return getTotalAggColumns(this.model.df, this.columns, aggrCols, this.model?.settings?.columns);
436
436
  }
437
437
 
438
438
  /** Creates viewer grid. */