@datagrok/proteomics 1.2.1 → 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.
- package/CHANGELOG.md +16 -0
- package/README.md +18 -5
- package/dist/package-test.js +1 -1
- package/dist/package-test.js.map +1 -1
- package/dist/package.js +1 -1
- package/dist/package.js.map +1 -1
- package/docs/personas-and-capabilities.md +16 -0
- package/docs/publishing-design-rationale.md +140 -0
- package/package.json +17 -3
- package/package.png +0 -0
- package/src/analysis/imputation.ts +3 -1
- package/src/analysis/normalization.ts +9 -4
- package/src/analysis/spc-storage.ts +1 -1
- package/src/menu.ts +5 -2
- package/src/package-api.ts +10 -2
- package/src/package-test.ts +3 -1
- package/src/package.g.ts +17 -3
- package/src/package.ts +31 -15
- package/src/panels/published-analysis-panel.ts +2 -2
- package/src/publishing/assert-published-shape.ts +2 -2
- package/src/publishing/package-settings-editor.ts +125 -0
- package/src/publishing/publish-project.ts +8 -8
- package/src/publishing/publish-settings.ts +57 -3
- package/src/publishing/publish-state.ts +45 -16
- package/src/publishing/reviewer-groups.ts +24 -0
- package/src/publishing/share-dialog.ts +42 -27
- package/src/publishing/trim-dataframe.ts +8 -1
- package/src/tests/abundance-correlation.ts +135 -0
- package/src/tests/analysis.ts +63 -0
- package/src/tests/enrichment-visualization.ts +95 -9
- package/src/tests/enrichment.ts +2 -1
- package/src/tests/project-vocabulary.ts +39 -0
- package/src/tests/publish-roundtrip.ts +40 -40
- package/src/tests/publish-spike.ts +2 -2
- package/src/tests/rank-abundance.ts +85 -0
- package/src/utils/abundance-detection.ts +145 -0
- package/src/viewers/abundance-correlation.ts +208 -0
- package/src/viewers/enrichment-viewers.ts +58 -24
- package/src/viewers/rank-abundance.ts +153 -0
- package/src/viewers/volcano.ts +3 -0
- package/test-console-output-1.log +1103 -1066
- package/test-record-1.mp4 +0 -0
- package/src/tests/group-mean-correlation.ts +0 -139
- 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
|
+
}
|
package/src/viewers/volcano.ts
CHANGED
|
@@ -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
|