@datagrok/proteomics 1.2.0 → 1.3.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +18 -5
  3. package/dist/package-test.js +1 -1
  4. package/dist/package-test.js.map +1 -1
  5. package/dist/package.js +1 -1
  6. package/dist/package.js.map +1 -1
  7. package/docs/personas-and-capabilities.md +16 -0
  8. package/docs/publishing-design-rationale.md +140 -0
  9. package/package.json +20 -5
  10. package/package.png +0 -0
  11. package/src/analysis/imputation.ts +3 -1
  12. package/src/analysis/normalization.ts +9 -4
  13. package/src/analysis/spc-storage.ts +1 -1
  14. package/src/menu.ts +5 -2
  15. package/src/package-api.ts +10 -2
  16. package/src/package-test.ts +3 -1
  17. package/src/package.g.ts +17 -3
  18. package/src/package.ts +31 -15
  19. package/src/panels/published-analysis-panel.ts +2 -2
  20. package/src/publishing/assert-published-shape.ts +2 -2
  21. package/src/publishing/package-settings-editor.ts +125 -0
  22. package/src/publishing/publish-project.ts +8 -8
  23. package/src/publishing/publish-settings.ts +57 -3
  24. package/src/publishing/publish-state.ts +45 -16
  25. package/src/publishing/reviewer-groups.ts +24 -0
  26. package/src/publishing/share-dialog.ts +42 -27
  27. package/src/publishing/trim-dataframe.ts +8 -1
  28. package/src/tests/abundance-correlation.ts +135 -0
  29. package/src/tests/analysis.ts +63 -0
  30. package/src/tests/enrichment-visualization.ts +95 -9
  31. package/src/tests/enrichment.ts +2 -1
  32. package/src/tests/project-vocabulary.ts +39 -0
  33. package/src/tests/publish-roundtrip.ts +40 -40
  34. package/src/tests/publish-spike.ts +2 -2
  35. package/src/tests/rank-abundance.ts +85 -0
  36. package/src/utils/abundance-detection.ts +145 -0
  37. package/src/viewers/abundance-correlation.ts +208 -0
  38. package/src/viewers/enrichment-viewers.ts +58 -24
  39. package/src/viewers/rank-abundance.ts +153 -0
  40. package/src/viewers/volcano.ts +3 -0
  41. package/test-console-output-1.log +1145 -1090
  42. package/test-record-1.mp4 +0 -0
  43. package/src/tests/group-mean-correlation.ts +0 -139
  44. package/src/viewers/group-mean-correlation.ts +0 -218
@@ -0,0 +1,153 @@
1
+ import * as grok from 'datagrok-api/grok';
2
+ import * as DG from 'datagrok-api/dg';
3
+
4
+ import {AbundanceByCondition, ConditionAbundance, findAbundanceByCondition} from '../utils/abundance-detection';
5
+
6
+ /** Percentiles driving the LOD line and the dynamic-range band. */
7
+ const LOD_PCT = 0.05;
8
+ const BAND_LO_PCT = 0.25;
9
+ const BAND_HI_PCT = 0.75;
10
+
11
+ /** Column-name prefixes for the derived per-condition plot columns. The log10
12
+ * columns are viewer VALUE bindings so they stay plain (a `~` name can break a
13
+ * value binding); both are `.hidden`-tagged to keep the protein grid clean. */
14
+ const LOG10_PREFIX = 'log10 intensity: ';
15
+ const RANK_PREFIX = 'rank: ';
16
+
17
+ function ensureFresh(df: DG.DataFrame, name: string, type: DG.ColumnType): DG.Column {
18
+ if (df.columns.contains(name)) df.columns.remove(name);
19
+ return type === DG.COLUMN_TYPE.INT ? df.columns.addNewInt(name) : df.columns.addNewFloat(name);
20
+ }
21
+
22
+ /** rank 1 = highest abundance; unquantified rows (null) sort last and get 0. */
23
+ function ranksByAbundance(log10: (number | null)[]): Int32Array {
24
+ const idx = log10.map((_, i) => i)
25
+ .filter((i) => log10[i] != null)
26
+ .sort((a, b) => (log10[b] as number) - (log10[a] as number));
27
+ const ranks = new Int32Array(log10.length); // 0 = not quantified
28
+ idx.forEach((origIdx, pos) => { ranks[origIdx] = pos + 1; });
29
+ return ranks;
30
+ }
31
+
32
+ /** nearest-rank percentile of the non-null values. */
33
+ function percentile(values: (number | null)[], p: number): number | null {
34
+ const sorted = values.filter((v): v is number => v != null).sort((a, b) => a - b);
35
+ if (sorted.length === 0) return null;
36
+ return sorted[Math.min(sorted.length - 1, Math.floor(p * (sorted.length - 1)))];
37
+ }
38
+
39
+ interface ConditionCols {
40
+ name: string;
41
+ rankCol: string;
42
+ log10Col: string;
43
+ lod: number | null;
44
+ bandLo: number | null;
45
+ bandHi: number | null;
46
+ }
47
+
48
+ /**
49
+ * Adds the derived per-condition `log10 intensity` and `rank` columns onto `df`
50
+ * (idempotent), and returns the plot-column names + LOD/band thresholds. The
51
+ * scatters bind to `df` itself so their current row is shared with the volcano
52
+ * — clicking a protein anywhere highlights it in the abundance plot for free.
53
+ */
54
+ function prepareConditionColumns(df: DG.DataFrame, cond: ConditionAbundance): ConditionCols {
55
+ const log10Name = LOG10_PREFIX + cond.name;
56
+ const rankName = RANK_PREFIX + cond.name;
57
+
58
+ const log10Col = ensureFresh(df, log10Name, DG.COLUMN_TYPE.FLOAT);
59
+ log10Col.init((i) => cond.log10Intensity[i]);
60
+ log10Col.setTag('.hidden', 'true');
61
+
62
+ const ranks = ranksByAbundance(cond.log10Intensity);
63
+ const rankCol = ensureFresh(df, rankName, DG.COLUMN_TYPE.INT);
64
+ rankCol.init((i) => ranks[i]);
65
+ rankCol.setTag('.hidden', 'true');
66
+
67
+ return {
68
+ name: cond.name,
69
+ rankCol: rankName,
70
+ log10Col: log10Name,
71
+ lod: percentile(cond.log10Intensity, LOD_PCT),
72
+ bandLo: percentile(cond.log10Intensity, BAND_LO_PCT),
73
+ bandHi: percentile(cond.log10Intensity, BAND_HI_PCT),
74
+ };
75
+ }
76
+
77
+ /** Removes any rank-abundance formula lines this module previously added (keyed
78
+ * by the `log10 intensity:` column reference) so re-runs don't stack lines. */
79
+ function clearRankAbundanceLines(df: DG.DataFrame): void {
80
+ const fl: any = (df as any).meta?.formulaLines;
81
+ if (!fl) return;
82
+ try {
83
+ const items: any[] = Array.isArray(fl.items) ? fl.items : [];
84
+ fl.items = items.filter((line) =>
85
+ !(typeof line?.formula === 'string' && line.formula.includes(`\${${LOG10_PREFIX}`)));
86
+ } catch { /* best effort */ }
87
+ }
88
+
89
+ function addLine(df: DG.DataFrame, yCol: string, value: number, color: string, style: string): void {
90
+ try {
91
+ (df as any).meta.formulaLines.addLine(
92
+ {formula: `\${${yCol}} = ${value}`, color, width: 1, style, visible: true});
93
+ } catch { /* best effort */ }
94
+ }
95
+
96
+ /** Creates one condition's rank-abundance scatter (x = rank, y = log10 intensity)
97
+ * with the LOD line + dynamic-range band, current-row highlighting on. */
98
+ export function createRankAbundanceScatter(df: DG.DataFrame, c: ConditionCols): DG.ScatterPlotViewer {
99
+ if (c.lod != null) addLine(df, c.log10Col, c.lod, '#d62728', 'dashed'); // LOD
100
+ if (c.bandLo != null) addLine(df, c.log10Col, c.bandLo, '#2ca02c', 'dotted');
101
+ if (c.bandHi != null) addLine(df, c.log10Col, c.bandHi, '#2ca02c', 'dotted');
102
+
103
+ return DG.Viewer.scatterPlot(df, {
104
+ x: c.rankCol,
105
+ y: c.log10Col,
106
+ showViewerFormulaLines: true,
107
+ showCurrentRowMarker: true,
108
+ markerType: 'circle',
109
+ markerDefaultSize: 5,
110
+ xAxisType: 'linear',
111
+ showRegressionLine: false,
112
+ title: `Rank–abundance — ${c.name}`,
113
+ } as any);
114
+ }
115
+
116
+ /**
117
+ * Docks the two per-condition rank-abundance (dynamic-range) S-curves side by
118
+ * side onto `view`, both bound to `df`. Returns true when charts were docked,
119
+ * false when the frame carries no resolvable abundance (caller warns).
120
+ */
121
+ export function dockRankAbundanceCharts(view: DG.TableView, df: DG.DataFrame): boolean {
122
+ const abundance: AbundanceByCondition | null = findAbundanceByCondition(df);
123
+ if (!abundance) return false;
124
+
125
+ clearRankAbundanceLines(df);
126
+ const c1 = prepareConditionColumns(df, abundance.group1);
127
+ const c2 = prepareConditionColumns(df, abundance.group2);
128
+
129
+ const node1 = view.dockManager.dock(
130
+ createRankAbundanceScatter(df, c1), DG.DOCK_TYPE.RIGHT, null, `Abundance — ${c1.name}`, 0.5);
131
+ view.dockManager.dock(
132
+ createRankAbundanceScatter(df, c2), DG.DOCK_TYPE.DOWN, node1, `Abundance — ${c2.name}`, 0.5);
133
+ return true;
134
+ }
135
+
136
+ /**
137
+ * Menu entry point: docks the rank-abundance charts on the active protein view,
138
+ * or warns when there is no abundance data (Report without groups / older
139
+ * Candidates export). Mirrors the enrichment-charts handler shape.
140
+ */
141
+ export function openRankAbundance(df: DG.DataFrame): void {
142
+ const view = grok.shell.tv;
143
+ if (!view || (view.dataFrame as any)?.dart !== (df as any).dart) {
144
+ grok.shell.warning('Open the protein table first, then run Rank–Abundance.');
145
+ return;
146
+ }
147
+ if (!dockRankAbundanceCharts(view, df)) {
148
+ grok.shell.warning(
149
+ 'No per-condition abundance found. Rank–Abundance needs the Spectronaut ' +
150
+ 'Candidates "AVG Group Quantity" columns, or an annotated Report (Annotate ' +
151
+ 'Experiment) with per-sample intensities.');
152
+ }
153
+ }
@@ -626,6 +626,9 @@ export function createVolcanoPlot(
626
626
 
627
627
  applyThresholdLines(df, yColName, fcThreshold, pThreshold);
628
628
  sp.props.showViewerFormulaLines = true;
629
+ // Keep filtered-out proteins visible (light gray) so a filter narrows focus
630
+ // without dropping the surrounding context.
631
+ sp.props.showFilteredOutPoints = true;
629
632
 
630
633
  // Hide the platform's interactive axis column-selector chips. They render the
631
634
  // raw column name ('log2FC' / 'negLog10P') next to each axis and collided with