@inflexa-ai/harness 0.42.1 → 0.43.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/README.md +2 -2
- package/dist/agents/conversation-agent.js +5 -1
- package/dist/agents/sandbox/drug-repurposing-agent.js +1 -0
- package/dist/agents/sandbox/shared.d.ts +1 -6
- package/dist/agents/sandbox/shared.js +3 -16
- package/dist/agents/sandbox/types.d.ts +1 -1
- package/dist/app/chat-turn.js +13 -1
- package/dist/config/environment-stores.d.ts +4 -2
- package/dist/contracts/chat-events.d.ts +9 -1
- package/dist/contracts/chat-frame.d.ts +3 -3
- package/dist/contracts/chat-frame.js +8 -3
- package/dist/contracts/chat-parts.d.ts +4 -1
- package/dist/contracts/data-profile.d.ts +4 -6
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/report-blocks.d.ts +1364 -75
- package/dist/contracts/report-blocks.js +221 -29
- package/dist/contracts/report-reference.js +1 -1
- package/dist/contracts/schemas/chat-events.d.ts +13 -0
- package/dist/contracts/schemas/chat-events.js +5 -0
- package/dist/contracts/schemas/chat-parts.d.ts +2 -2
- package/dist/contracts/schemas/index.d.ts +1 -1
- package/dist/contracts/schemas/index.js +1 -1
- package/dist/execution/input-signature.d.ts +7 -13
- package/dist/execution/input-signature.js +0 -0
- package/dist/execution/run-canceler.d.ts +3 -2
- package/dist/execution/run-canceler.js +3 -2
- package/dist/execution/run-event-stream.d.ts +16 -2
- package/dist/execution/run-event-stream.js +54 -3
- package/dist/index.d.ts +1 -8
- package/dist/index.js +5 -16
- package/dist/input-scan/enrich.d.ts +0 -2
- package/dist/input-scan/enrich.js +0 -1
- package/dist/lib/metrics.js +6 -7
- package/dist/lib/otel-spans.d.ts +4 -9
- package/dist/lib/otel-spans.js +3 -8
- package/dist/loop/run-agent.d.ts +4 -2
- package/dist/loop/run-agent.js +38 -23
- package/dist/memory/thread-history.d.ts +1 -1
- package/dist/prompts/report-session.d.ts +1 -1
- package/dist/prompts/report-session.js +17 -0
- package/dist/prompts/sandbox/drug-repurposing-agent.d.ts +1 -1
- package/dist/prompts/sandbox/drug-repurposing-agent.js +7 -0
- package/dist/provenance/exec-frame.d.ts +1 -1
- package/dist/report-model/authoring-grammar.d.ts +416 -17
- package/dist/report-model/authoring-grammar.js +25 -4
- package/dist/report-model/block-walk.d.ts +20 -4
- package/dist/report-model/block-walk.js +64 -7
- package/dist/report-model/draft-finish.d.ts +6 -2
- package/dist/report-model/draft-finish.js +6 -1
- package/dist/report-model/production-resolver.d.ts +8 -0
- package/dist/report-model/production-resolver.js +1 -1
- package/dist/report-model/validate.d.ts +20 -10
- package/dist/report-model/validate.js +29 -13
- package/dist/report-render/chart-export.d.ts +52 -0
- package/dist/report-render/chart-export.js +192 -0
- package/dist/report-render/chart-presets.d.ts +46 -47
- package/dist/report-render/chart-presets.js +31 -101
- package/dist/report-render/chart-renderers.d.ts +163 -0
- package/dist/report-render/chart-renderers.js +1055 -0
- package/dist/report-render/chart-toolbox.d.ts +124 -0
- package/dist/report-render/chart-toolbox.js +595 -0
- package/dist/report-render/chart.d.ts +60 -27
- package/dist/report-render/chart.js +2323 -524
- package/dist/report-render/design.d.ts +476 -16
- package/dist/report-render/design.js +390 -56
- package/dist/report-render/figures/common.d.ts +321 -0
- package/dist/report-render/figures/common.js +759 -0
- package/dist/report-render/figures/dendrogram.d.ts +36 -0
- package/dist/report-render/figures/dendrogram.js +130 -0
- package/dist/report-render/figures/dense.d.ts +111 -0
- package/dist/report-render/figures/dense.js +204 -0
- package/dist/report-render/figures/dotplot.d.ts +28 -0
- package/dist/report-render/figures/dotplot.js +234 -0
- package/dist/report-render/figures/embedding.d.ts +43 -0
- package/dist/report-render/figures/embedding.js +360 -0
- package/dist/report-render/figures/equal-units.d.ts +101 -0
- package/dist/report-render/figures/equal-units.js +215 -0
- package/dist/report-render/figures/forest.d.ts +20 -0
- package/dist/report-render/figures/forest.js +398 -0
- package/dist/report-render/figures/gsea.d.ts +30 -0
- package/dist/report-render/figures/gsea.js +391 -0
- package/dist/report-render/figures/heatmap.d.ts +40 -0
- package/dist/report-render/figures/heatmap.js +395 -0
- package/dist/report-render/figures/index.d.ts +135 -0
- package/dist/report-render/figures/index.js +100 -0
- package/dist/report-render/figures/km.d.ts +16 -0
- package/dist/report-render/figures/km.js +406 -0
- package/dist/report-render/figures/label-room.d.ts +130 -0
- package/dist/report-render/figures/label-room.js +341 -0
- package/dist/report-render/figures/locuszoom.d.ts +50 -0
- package/dist/report-render/figures/locuszoom.js +675 -0
- package/dist/report-render/figures/lollipop.d.ts +16 -0
- package/dist/report-render/figures/lollipop.js +247 -0
- package/dist/report-render/figures/ma.d.ts +20 -0
- package/dist/report-render/figures/ma.js +114 -0
- package/dist/report-render/figures/manhattan.d.ts +60 -0
- package/dist/report-render/figures/manhattan.js +343 -0
- package/dist/report-render/figures/oncoprint.d.ts +41 -0
- package/dist/report-render/figures/oncoprint.js +420 -0
- package/dist/report-render/figures/pca.d.ts +37 -0
- package/dist/report-render/figures/pca.js +241 -0
- package/dist/report-render/figures/qq.d.ts +30 -0
- package/dist/report-render/figures/qq.js +182 -0
- package/dist/report-render/figures/roc.d.ts +17 -0
- package/dist/report-render/figures/roc.js +116 -0
- package/dist/report-render/figures/sankey.d.ts +48 -0
- package/dist/report-render/figures/sankey.js +353 -0
- package/dist/report-render/figures/upset.d.ts +60 -0
- package/dist/report-render/figures/upset.js +316 -0
- package/dist/report-render/figures/volcano.d.ts +25 -0
- package/dist/report-render/figures/volcano.js +166 -0
- package/dist/report-render/hybrid-svg.d.ts +75 -0
- package/dist/report-render/hybrid-svg.js +504 -0
- package/dist/report-render/number-format.d.ts +19 -0
- package/dist/report-render/number-format.js +25 -1
- package/dist/report-render/page.d.ts +41 -4
- package/dist/report-render/page.js +247 -12
- package/dist/report-render/render.d.ts +13 -4
- package/dist/report-render/render.js +37 -6
- package/dist/report-render/types.d.ts +23 -2
- package/dist/report-render/value-bridge.d.ts +38 -3
- package/dist/report-render/value-bridge.js +137 -6
- package/dist/report-render/views/chart-view.d.ts +42 -6
- package/dist/report-render/views/chart-view.js +70 -9
- package/dist/report-render/views/page-view.js +9 -10
- package/dist/runtime/assemble.js +2 -2
- package/dist/sandbox/client.d.ts +23 -32
- package/dist/sandbox/client.js +3 -11
- package/dist/sandbox/create-sandbox.d.ts +9 -24
- package/dist/sandbox/create-sandbox.js +15 -30
- package/dist/sandbox/docker-client.d.ts +12 -22
- package/dist/sandbox/docker-client.js +29 -43
- package/dist/sandbox/exec.d.ts +58 -0
- package/dist/sandbox/exec.js +197 -0
- package/dist/sandbox/identity.d.ts +5 -5
- package/dist/sandbox/identity.js +7 -10
- package/dist/sandbox/k8s-client.d.ts +4 -10
- package/dist/sandbox/k8s-client.js +5 -12
- package/dist/sandbox/liveness.d.ts +5 -12
- package/dist/sandbox/liveness.js +5 -12
- package/dist/sandbox/notification-sweep.d.ts +4 -7
- package/dist/sandbox/notification-sweep.js +4 -7
- package/dist/sandbox/reaper.d.ts +3 -3
- package/dist/sandbox/reaper.js +3 -3
- package/dist/sandbox/sandbox-error.d.ts +10 -11
- package/dist/sandbox/sandbox-error.js +10 -11
- package/dist/sandbox/sandbox-step-translate.d.ts +1 -1
- package/dist/sandbox/sandbox-step-translate.js +1 -1
- package/dist/sandbox/types.d.ts +31 -99
- package/dist/sandbox/types.js +17 -84
- package/dist/state/active-sandboxes.d.ts +7 -32
- package/dist/state/active-sandboxes.js +11 -46
- package/dist/state/index.d.ts +1 -2
- package/dist/state/index.js +1 -1
- package/dist/state/migrations/20261005120000_drop_the_active_exec_tracking.d.ts +7 -0
- package/dist/state/migrations/20261005120000_drop_the_active_exec_tracking.js +12 -0
- package/dist/state/migrations/index.js +2 -0
- package/dist/state/schema.d.ts +1 -5
- package/dist/state/schema.js +1 -6
- package/dist/state/step-executions.js +1 -2
- package/dist/tasks/data-profile.js +7 -20
- package/dist/tasks/derive-table-exec.d.ts +4 -14
- package/dist/tasks/derive-table-exec.js +9 -24
- package/dist/tasks/extract-values.d.ts +2 -2
- package/dist/tasks/extract-values.js +4 -7
- package/dist/tools/bio/index.d.ts +2 -0
- package/dist/tools/bio/index.js +3 -0
- package/dist/tools/bio/keys.d.ts +11 -0
- package/dist/tools/bio/keys.js +15 -0
- package/dist/tools/bio/search-clinical-trials.d.ts +1 -1
- package/dist/tools/bio/search-patents.d.ts +46 -0
- package/dist/tools/bio/search-patents.js +134 -0
- package/dist/tools/bio/search-regulatory-approvals.d.ts +49 -0
- package/dist/tools/bio/search-regulatory-approvals.js +158 -0
- package/dist/tools/define-tool.d.ts +56 -13
- package/dist/tools/define-tool.js +28 -1
- package/dist/tools/execute-analysis.js +3 -3
- package/dist/tools/lib/amass-client.d.ts +586 -0
- package/dist/tools/lib/amass-client.js +492 -0
- package/dist/tools/lib/amass-config.d.ts +2 -0
- package/dist/tools/lib/amass-config.js +10 -0
- package/dist/tools/report-session/derive-table.d.ts +8 -9
- package/dist/tools/report-session/derive-table.js +3 -4
- package/dist/tools/report-session/preview-report.js +4 -49
- package/dist/tools/sandbox/list-available-packages.d.ts +1 -1
- package/dist/tools/sandbox/list-available-packages.js +5 -4
- package/dist/tools/workspace/edit-file.d.ts +5 -4
- package/dist/tools/workspace/edit-file.js +10 -13
- package/dist/tools/workspace/execute-command.d.ts +27 -42
- package/dist/tools/workspace/execute-command.js +42 -38
- package/dist/tools/workspace/index.d.ts +5 -6
- package/dist/tools/workspace/index.js +5 -6
- package/dist/tools/workspace/mutator.d.ts +43 -8
- package/dist/tools/workspace/mutator.js +45 -34
- package/dist/tools/workspace/run-exec.d.ts +3 -12
- package/dist/tools/workspace/run-exec.js +7 -24
- package/dist/tools/workspace/scan-inputs.d.ts +0 -3
- package/dist/tools/workspace/scan-inputs.js +0 -1
- package/dist/tools/workspace/write-file.d.ts +12 -3
- package/dist/tools/workspace/write-file.js +17 -13
- package/dist/workflows/sandbox-step.d.ts +7 -11
- package/dist/workflows/sandbox-step.js +24 -27
- package/package.json +3 -1
- package/dist/sandbox/await-exec.d.ts +0 -143
- package/dist/sandbox/await-exec.js +0 -417
- package/dist/sandbox/deliver-exec-event.d.ts +0 -25
- package/dist/sandbox/deliver-exec-event.js +0 -29
- package/dist/sandbox/digest.d.ts +0 -14
- package/dist/sandbox/digest.js +0 -17
- package/dist/sandbox/exec-id.d.ts +0 -12
- package/dist/sandbox/exec-id.js +0 -20
- package/dist/sandbox/hmac.d.ts +0 -62
- package/dist/sandbox/hmac.js +0 -72
- package/dist/sandbox/submit-exec.d.ts +0 -38
- package/dist/sandbox/submit-exec.js +0 -80
- package/dist/sandbox/watchdog.d.ts +0 -71
- package/dist/sandbox/watchdog.js +0 -161
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Manhattan figure, as qqman and the GWAS papers draw it: the cumulative genome position on x, the
|
|
3
|
+
* transformed p on y, and one block of points for each chromosome.
|
|
4
|
+
*
|
|
5
|
+
* - The chromosomes alternate between two colors, thus each boundary reads without a tick.
|
|
6
|
+
* - Each chromosome name sits under the middle of its points, and the position ticks hide.
|
|
7
|
+
* - Two guide lines mark the genome-wide significance at 5e-8 and the suggestive level at 1e-5. The
|
|
8
|
+
* genome-wide line carries its label.
|
|
9
|
+
* - The lead variant of each chromosome that passes the genome-wide line is the row with the smallest p of
|
|
10
|
+
* that chromosome, as the `annotateTop` rule of qqman gives it. The ten most significant lead variants
|
|
11
|
+
* show their names over their peaks, clear of each other and of the points, with a leader line to each
|
|
12
|
+
* peak. The label of the genome-wide line sits over the line, clear of them.
|
|
13
|
+
* - A chromosome name prints where it keeps a gap from the name before it, thus the small chromosomes never
|
|
14
|
+
* print two names on top of each other.
|
|
15
|
+
* - The lead names and the chromosome names place again for the plot and the text size of each export. A render
|
|
16
|
+
* tries the most significant lead names whose summed text width fits the plot width, and a name that finds no
|
|
17
|
+
* place clear of the earlier names does not print. Thus a narrow export prints fewer names.
|
|
18
|
+
* - The chromosomes read in genome order whatever the order of the rows: the numbered ones, then X, Y, and MT,
|
|
19
|
+
* then each other name. The colors, the names, and the leads read that order.
|
|
20
|
+
* - A variant whose stored p is 0 draws an upward triangle at the top of the plotted range, and it leads its
|
|
21
|
+
* chromosome.
|
|
22
|
+
* - While a zoom of the page toolbox narrows the x axis, the axis shows each position as `chrN: P Mb`, and the
|
|
23
|
+
* chromosome names hide.
|
|
24
|
+
*
|
|
25
|
+
* The `group` channel names the chromosome, and the figure needs it. The points come from the composition
|
|
26
|
+
* machinery, thus a table of many thousand variants reads the shared payload. The lines ride the point series
|
|
27
|
+
* as mark members, and the chromosome names and the lead names ride one series each after them. Thus the page
|
|
28
|
+
* draws each of them over the payload points.
|
|
29
|
+
*/
|
|
30
|
+
import { err, ok } from "neverthrow";
|
|
31
|
+
import { channelColumn } from "../../contracts/report-blocks.js";
|
|
32
|
+
import { MANHATTAN_P_THRESHOLD } from "../chart-presets.js";
|
|
33
|
+
import { GENOME_MEMBER, GENOME_POSITION_FUNCTION } from "../chart-toolbox.js";
|
|
34
|
+
import { CHART_INK, CHART_PALETTE, MUTED_CHART_COLOR } from "../design.js";
|
|
35
|
+
import { formatNumberCell, typographicExponent } from "../number-format.js";
|
|
36
|
+
import { categoryName, transformColumn } from "./common.js";
|
|
37
|
+
import { axisOf, BELOW_RESOLUTION_NAME, BELOW_RESOLUTION_SYMBOL, BELOW_RESOLUTION_SYMBOL_PX, BELOW_RESOLUTION_TOOLTIP, belowResolutionHeight, belowResolutionRows, DENSE_NULL_SYMBOL_PX, DENSE_SIGNAL_OPACITY, demandedChannel, largest, niceCeiling, plottedValues, pointLayer, POINT_NAMES, pointNameSeries, seriesOf, sizedSeries, smallest, } from "./dense.js";
|
|
38
|
+
import { placeLeaderNames, textWidthPx } from "./label-room.js";
|
|
39
|
+
/** The two colors that alternate between the chromosomes: the blue of the palette and the muted gray. */
|
|
40
|
+
export const MANHATTAN_COLORS = [CHART_PALETTE[0], MUTED_CHART_COLOR];
|
|
41
|
+
/** The suggestive level of a GWAS, the second guide line of qqman. */
|
|
42
|
+
export const MANHATTAN_SUGGESTIVE_P = 1e-5;
|
|
43
|
+
/** The x title where the binding declares no label. The axis shows the chromosome names, not a position. */
|
|
44
|
+
const CHROMOSOME_TITLE = "Chromosome";
|
|
45
|
+
/** The y title of the transformed p. The minus sign is the character, not a hyphen. */
|
|
46
|
+
const NEG_LOG10_P_TITLE = "−log10(p)";
|
|
47
|
+
/** The members of a Manhattan block that the figure reads. */
|
|
48
|
+
const READS = new Set(["x", "y", "group", "label"]);
|
|
49
|
+
/** The largest count of lead variants that show their name, over the whole genome. */
|
|
50
|
+
export const MANHATTAN_LABEL_COUNT = 10;
|
|
51
|
+
/**
|
|
52
|
+
* The lead names that a render tries to place: the most significant ones, in order, while the summed width of
|
|
53
|
+
* their text fits the width of the plot. A narrow export thus tries fewer names, and each of them keeps room
|
|
54
|
+
* beside its peak.
|
|
55
|
+
*/
|
|
56
|
+
function leadsThatFit(names, frame) {
|
|
57
|
+
let used = 0;
|
|
58
|
+
let count = 0;
|
|
59
|
+
for (const name of names) {
|
|
60
|
+
used += textWidthPx(name.text, frame.textPx);
|
|
61
|
+
if (used > frame.widthPx)
|
|
62
|
+
break;
|
|
63
|
+
count += 1;
|
|
64
|
+
}
|
|
65
|
+
return names.slice(0, count);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The smallest gap between two chromosome names that both print, as a share of the text size.
|
|
69
|
+
*
|
|
70
|
+
* The small chromosomes lie close together. The name of a chromosome whose text comes nearer than this gap to
|
|
71
|
+
* the text of the last printed name, in the plot of a render, does not print in that render.
|
|
72
|
+
*/
|
|
73
|
+
export const MANHATTAN_NAME_GAP_SHARE = 0.5;
|
|
74
|
+
/**
|
|
75
|
+
* The label place of the genome-wide guide: inside the plot at the right end, over the line.
|
|
76
|
+
*
|
|
77
|
+
* The band under the line is a few pixels high, and a label there runs into the axis and the chromosome names.
|
|
78
|
+
* The box of the label over the line is reserved when the lead names place, thus no name covers it.
|
|
79
|
+
*/
|
|
80
|
+
const GUIDE_OVER_LINE = "insideEndTop";
|
|
81
|
+
/** The gap between a guide line and its label, in pixels: the default distance of the chart runtime. */
|
|
82
|
+
const GUIDE_LABEL_DISTANCE_PX = 5;
|
|
83
|
+
/** The height of one line of the label text, as a share of the text size. */
|
|
84
|
+
const GUIDE_LINE_SHARE = 1.3;
|
|
85
|
+
/**
|
|
86
|
+
* The box in data units that the label of the genome-wide line takes at the right end of the line, over it, in
|
|
87
|
+
* the plot of one render.
|
|
88
|
+
*/
|
|
89
|
+
export function guideLabelBox(line, x, top, text, frame) {
|
|
90
|
+
const width = (textWidthPx(text, frame.textPx) + GUIDE_LABEL_DISTANCE_PX) / frame.widthPx;
|
|
91
|
+
const height = (GUIDE_LINE_SHARE * frame.textPx + GUIDE_LABEL_DISTANCE_PX) / frame.heightPx;
|
|
92
|
+
return { left: x.max - width * (x.max - x.min), right: x.max, bottom: line, top: line + height * top };
|
|
93
|
+
}
|
|
94
|
+
/** The name of the series that carries the chromosome names. */
|
|
95
|
+
const CHROMOSOME_NAMES = "Chromosome names";
|
|
96
|
+
/**
|
|
97
|
+
* The chromosomes whose name prints in the plot of one render, in order. A name prints where its text starts at
|
|
98
|
+
* least `MANHATTAN_NAME_GAP_SHARE` of the text size past the end of the text of the last printed name. Each text
|
|
99
|
+
* centers under the middle of its chromosome. The first chromosome always prints.
|
|
100
|
+
*/
|
|
101
|
+
function printedNames(found, x, frame) {
|
|
102
|
+
const toPx = (value) => ((value - x.min) / (x.max - x.min)) * frame.widthPx;
|
|
103
|
+
const gap = MANHATTAN_NAME_GAP_SHARE * frame.textPx;
|
|
104
|
+
const printed = [];
|
|
105
|
+
let lastEnd;
|
|
106
|
+
for (const chromosome of found) {
|
|
107
|
+
const middle = toPx((chromosome.low + chromosome.high) / 2);
|
|
108
|
+
const half = textWidthPx(categoryName(chromosome.name), frame.textPx) / 2;
|
|
109
|
+
if (lastEnd === undefined || middle - half - lastEnd >= gap) {
|
|
110
|
+
printed.push(chromosome);
|
|
111
|
+
lastEnd = middle + half;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return printed;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The series of the chromosome names: one point with no symbol under the middle of each printed chromosome,
|
|
118
|
+
* at the x axis, with the name as its label.
|
|
119
|
+
*
|
|
120
|
+
* The gap rule decides which names print in each render, thus two names never print on top of each other. The
|
|
121
|
+
* runtime also measures the labels and hides a name that overlaps another, for a page narrower than its frame.
|
|
122
|
+
* The series follows the points in the series list, thus the points still read the shared payload.
|
|
123
|
+
*/
|
|
124
|
+
function chromosomeNames(found) {
|
|
125
|
+
return {
|
|
126
|
+
type: "scatter",
|
|
127
|
+
name: CHROMOSOME_NAMES,
|
|
128
|
+
silent: true,
|
|
129
|
+
symbolSize: 0,
|
|
130
|
+
tooltip: { show: false },
|
|
131
|
+
label: { show: true, position: "bottom", distance: 6, color: CHART_INK, formatter: "{b}" },
|
|
132
|
+
labelLayout: { hideOverlap: true },
|
|
133
|
+
data: found.map((chromosome) => ({ name: categoryName(chromosome.name), value: [(chromosome.low + chromosome.high) / 2, 0] })),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* The genome member of the x axis: the start and the axis label of each chromosome, in position order.
|
|
138
|
+
*
|
|
139
|
+
* The table gives the cumulative position alone. Thus a chromosome starts at the last drawn position of the
|
|
140
|
+
* chromosome before it, and the first one starts at 0, as qqman adds up the positions.
|
|
141
|
+
*/
|
|
142
|
+
function genomeAxis(found) {
|
|
143
|
+
const byPlace = [...found].sort((a, b) => a.low - b.low);
|
|
144
|
+
return {
|
|
145
|
+
names: CHROMOSOME_NAMES,
|
|
146
|
+
starts: byPlace.map((chromosome, index) => [
|
|
147
|
+
index === 0 ? 0 : byPlace[index - 1].high,
|
|
148
|
+
`chr${categoryName(chromosome.name).trim().replace(CHROMOSOME_PREFIX, "")}`,
|
|
149
|
+
]),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/** The sides of a peak where a lead name sits, in the order that the figure tries them. */
|
|
153
|
+
const LEAD_NAME_SIDES = ["top", "right", "left"];
|
|
154
|
+
/** The place of the three named chromosomes after the numbered ones, in genome order. */
|
|
155
|
+
const NAMED_CHROMOSOMES = { X: 0, Y: 1, MT: 2, M: 2 };
|
|
156
|
+
/** The text prefix of a chromosome name that some tables carry, for example `chr1`. */
|
|
157
|
+
const CHROMOSOME_PREFIX = /^chr/i;
|
|
158
|
+
/**
|
|
159
|
+
* The genome order of two chromosome names: the numbered chromosomes in numeric order, then X, Y, and MT, then
|
|
160
|
+
* each other name in text order. A `chr` prefix takes no part in the order.
|
|
161
|
+
*/
|
|
162
|
+
export function compareChromosomes(a, b) {
|
|
163
|
+
const rank = (cell) => {
|
|
164
|
+
const name = String(cell).trim().replace(CHROMOSOME_PREFIX, "");
|
|
165
|
+
if (/^\d+$/.test(name))
|
|
166
|
+
return [0, Number(name), name];
|
|
167
|
+
const named = NAMED_CHROMOSOMES[name.toUpperCase()];
|
|
168
|
+
return named !== undefined ? [1, named, name] : [2, 0, name];
|
|
169
|
+
};
|
|
170
|
+
const [left, right] = [rank(a), rank(b)];
|
|
171
|
+
if (left[0] !== right[0])
|
|
172
|
+
return left[0] - right[0];
|
|
173
|
+
if (left[1] !== right[1])
|
|
174
|
+
return left[1] - right[1];
|
|
175
|
+
return left[2] < right[2] ? -1 : left[2] > right[2] ? 1 : 0;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* The chromosomes of the table in genome order, whatever the order of the rows.
|
|
179
|
+
*
|
|
180
|
+
* The extent reads each row that draws a point. The lead is the row with the largest transformed p above the
|
|
181
|
+
* genome-wide line whose name is not empty. A tie keeps the row at the smaller position, then the first row.
|
|
182
|
+
*/
|
|
183
|
+
function chromosomes(rows, groupColumn, labelColumn, xs, ys, line) {
|
|
184
|
+
const byKey = new Map();
|
|
185
|
+
const order = [];
|
|
186
|
+
for (let index = 0; index < rows.length; index += 1) {
|
|
187
|
+
const value = rows[index][groupColumn];
|
|
188
|
+
const x = xs[index];
|
|
189
|
+
const y = ys[index];
|
|
190
|
+
if (value === undefined || x === null || y === null)
|
|
191
|
+
continue;
|
|
192
|
+
// The key carries the type of the cell, as the series split does.
|
|
193
|
+
const key = `${typeof value}:${String(value)}`;
|
|
194
|
+
let chromosome = byKey.get(key);
|
|
195
|
+
if (chromosome === undefined) {
|
|
196
|
+
chromosome = { name: value, low: x, high: x };
|
|
197
|
+
byKey.set(key, chromosome);
|
|
198
|
+
order.push(chromosome);
|
|
199
|
+
}
|
|
200
|
+
chromosome.low = Math.min(chromosome.low, x);
|
|
201
|
+
chromosome.high = Math.max(chromosome.high, x);
|
|
202
|
+
const name = labelColumn === undefined ? undefined : rows[index][labelColumn];
|
|
203
|
+
if (y > line && name !== undefined && String(name).trim() !== "" && (chromosome.lead === undefined || leads(index, chromosome.lead, xs, ys))) {
|
|
204
|
+
chromosome.lead = index;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return order.sort((a, b) => compareChromosomes(a.name, b.name));
|
|
208
|
+
}
|
|
209
|
+
/** True when one row leads before another: a larger transformed p, then a smaller position, then the earlier row. */
|
|
210
|
+
function leads(index, other, xs, ys) {
|
|
211
|
+
// A stored zero ranks at +Infinity, and the difference of two such ranks is NaN, thus the ranks compare.
|
|
212
|
+
const [p, otherP] = [ys[index] ?? 0, ys[other] ?? 0];
|
|
213
|
+
if (p !== otherP)
|
|
214
|
+
return p > otherP;
|
|
215
|
+
const byPosition = (xs[other] ?? 0) - (xs[index] ?? 0);
|
|
216
|
+
return byPosition !== 0 ? byPosition > 0 : index < other;
|
|
217
|
+
}
|
|
218
|
+
/** The guide lines of a mark member, with the label of the genome-wide line over the line. */
|
|
219
|
+
function guideOverLine(markLine, line) {
|
|
220
|
+
const data = Array.isArray(markLine.data) ? markLine.data : [];
|
|
221
|
+
return {
|
|
222
|
+
...markLine,
|
|
223
|
+
data: data.map((entry) => entry.yAxis === line && typeof entry.label === "object" && entry.label !== null
|
|
224
|
+
? { ...entry, label: { ...entry.label, position: GUIDE_OVER_LINE } }
|
|
225
|
+
: entry),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
/** The text of a guide p-value: the p in the scientific form of the number helper, as a power of ten. */
|
|
229
|
+
function guideText(p) {
|
|
230
|
+
return `p ${typographicExponent(formatNumberCell(p, "scientific").text)}`;
|
|
231
|
+
}
|
|
232
|
+
function deriveManhattan(block, rows, context) {
|
|
233
|
+
const encoding = block.encoding ?? {};
|
|
234
|
+
const x = demandedChannel(context.blockId, "manhattan", encoding, "x");
|
|
235
|
+
if (x.isErr())
|
|
236
|
+
return err(x.error);
|
|
237
|
+
const y = demandedChannel(context.blockId, "manhattan", encoding, "y");
|
|
238
|
+
if (y.isErr())
|
|
239
|
+
return err(y.error);
|
|
240
|
+
const group = demandedChannel(context.blockId, "manhattan", encoding, "group");
|
|
241
|
+
if (group.isErr())
|
|
242
|
+
return err(group.error);
|
|
243
|
+
const pColumn = channelColumn(y.value);
|
|
244
|
+
const line = -Math.log10(MANHATTAN_P_THRESHOLD);
|
|
245
|
+
const suggestive = -Math.log10(MANHATTAN_SUGGESTIVE_P);
|
|
246
|
+
const xs = plottedValues(rows, x.value);
|
|
247
|
+
const finite = transformColumn(rows, pColumn, "neg_log10");
|
|
248
|
+
const peak = largest(finite.flatMap((value, index) => (value !== null && xs[index] !== null ? [value] : []))) ?? 0;
|
|
249
|
+
const zeroRows = belowResolutionRows(rows, pColumn, xs);
|
|
250
|
+
const zeros = new Set(zeroRows);
|
|
251
|
+
const zeroY = belowResolutionHeight(peak, line);
|
|
252
|
+
// A stored zero draws at the top of the plotted range, and it outranks each finite p as a lead.
|
|
253
|
+
const ys = finite.map((value, index) => (zeros.has(index) ? zeroY : value));
|
|
254
|
+
const ranks = finite.map((value, index) => (zeros.has(index) ? Number.POSITIVE_INFINITY : value));
|
|
255
|
+
const groupColumn = channelColumn(group.value);
|
|
256
|
+
const found = chromosomes(rows, groupColumn, encoding.label, xs, ranks, line);
|
|
257
|
+
const composition = {
|
|
258
|
+
series: [
|
|
259
|
+
{
|
|
260
|
+
form: "scatter",
|
|
261
|
+
encoding: {
|
|
262
|
+
x: x.value,
|
|
263
|
+
y: { column: pColumn, transform: "neg_log10" },
|
|
264
|
+
group: group.value,
|
|
265
|
+
...(encoding.label !== undefined ? { label: encoding.label } : {}),
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
],
|
|
269
|
+
annotations: [
|
|
270
|
+
{ kind: "reference-line", axis: "y", value: line, label: guideText(MANHATTAN_P_THRESHOLD) },
|
|
271
|
+
// The two lines lie a few pixels apart, thus a second label would cover the first one.
|
|
272
|
+
{ kind: "reference-line", axis: "y", value: suggestive },
|
|
273
|
+
],
|
|
274
|
+
};
|
|
275
|
+
// The most significant lead variants show their names, one for each chromosome at most.
|
|
276
|
+
const leadRows = found
|
|
277
|
+
.flatMap((chromosome) => (chromosome.lead !== undefined ? [chromosome.lead] : []))
|
|
278
|
+
.sort((a, b) => (leads(a, b, xs, ranks) ? -1 : 1))
|
|
279
|
+
.slice(0, MANHATTAN_LABEL_COUNT);
|
|
280
|
+
const composed = context.compose(composition, { preset: { x: CHROMOSOME_TITLE, y: NEG_LOG10_P_TITLE } });
|
|
281
|
+
if (composed.isErr())
|
|
282
|
+
return err(composed.error);
|
|
283
|
+
const option = composed.value;
|
|
284
|
+
// The composition splits the series in the order of the rows, thus each series takes the color of the
|
|
285
|
+
// genome place of its chromosome, and never of its place in the series list.
|
|
286
|
+
const places = new Map(found.map((chromosome, place) => [categoryName(chromosome.name), place]));
|
|
287
|
+
const colorOf = (name) => MANHATTAN_COLORS[(places.get(String(name)) ?? 0) % MANHATTAN_COLORS.length];
|
|
288
|
+
const low = smallest(found.map((chromosome) => chromosome.low));
|
|
289
|
+
const high = largest(found.map((chromosome) => chromosome.high));
|
|
290
|
+
const top = niceCeiling(Math.max(peak, line, zeroRows.length > 0 ? zeroY : 0));
|
|
291
|
+
const labelColumn = encoding.label;
|
|
292
|
+
const names = labelColumn === undefined ? [] : leadRows.map((index) => ({ x: xs[index] ?? 0, y: ys[index] ?? 0, text: String(rows[index][labelColumn]) }));
|
|
293
|
+
const span = low !== undefined && high !== undefined ? { min: low, max: high } : undefined;
|
|
294
|
+
const text = sizedSeries([CHROMOSOME_NAMES, POINT_NAMES], (frame) => {
|
|
295
|
+
if (span === undefined)
|
|
296
|
+
return [];
|
|
297
|
+
const tried = leadsThatFit(names, frame);
|
|
298
|
+
const placed = placeLeaderNames(tried, { xs, ys, pointPx: DENSE_NULL_SYMBOL_PX }, { x: span, y: { min: 0, max: top } }, LEAD_NAME_SIDES, top, frame, [
|
|
299
|
+
guideLabelBox(line, span, top, guideText(MANHATTAN_P_THRESHOLD), frame),
|
|
300
|
+
]);
|
|
301
|
+
return [chromosomeNames(printedNames(found, span, frame)), ...pointNameSeries(tried, placed)];
|
|
302
|
+
});
|
|
303
|
+
return ok({
|
|
304
|
+
...option,
|
|
305
|
+
...text.member,
|
|
306
|
+
legend: { show: false },
|
|
307
|
+
xAxis: {
|
|
308
|
+
...axisOf(option, "xAxis"),
|
|
309
|
+
...(low !== undefined && high !== undefined ? { min: low, max: high } : {}),
|
|
310
|
+
axisLabel: { show: false, formatter: GENOME_POSITION_FUNCTION },
|
|
311
|
+
axisTick: { show: false },
|
|
312
|
+
[GENOME_MEMBER]: genomeAxis(found),
|
|
313
|
+
},
|
|
314
|
+
yAxis: { ...axisOf(option, "yAxis"), min: 0, max: top },
|
|
315
|
+
series: [
|
|
316
|
+
...seriesOf(option).map((series) => ({
|
|
317
|
+
...series,
|
|
318
|
+
...pointLayer(colorOf(series.name), DENSE_NULL_SYMBOL_PX, DENSE_SIGNAL_OPACITY),
|
|
319
|
+
...(series.markLine !== undefined ? { markLine: guideOverLine(series.markLine, line) } : {}),
|
|
320
|
+
})),
|
|
321
|
+
...(zeroRows.length > 0
|
|
322
|
+
? [
|
|
323
|
+
{
|
|
324
|
+
type: "scatter",
|
|
325
|
+
name: BELOW_RESOLUTION_NAME,
|
|
326
|
+
symbol: BELOW_RESOLUTION_SYMBOL,
|
|
327
|
+
symbolSize: BELOW_RESOLUTION_SYMBOL_PX,
|
|
328
|
+
itemStyle: { opacity: DENSE_SIGNAL_OPACITY },
|
|
329
|
+
tooltip: BELOW_RESOLUTION_TOOLTIP,
|
|
330
|
+
data: zeroRows.map((index) => ({
|
|
331
|
+
...(labelColumn !== undefined ? { name: String(rows[index][labelColumn]) } : {}),
|
|
332
|
+
value: [xs[index] ?? 0, zeroY],
|
|
333
|
+
itemStyle: { color: colorOf(categoryName(rows[index][groupColumn])) },
|
|
334
|
+
})),
|
|
335
|
+
},
|
|
336
|
+
]
|
|
337
|
+
: []),
|
|
338
|
+
...text.page,
|
|
339
|
+
],
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
/** The Manhattan figure module. */
|
|
343
|
+
export const MANHATTAN_FIGURE = { reads: READS, derive: deriveManhattan };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The oncoprint: the alteration matrix of a cohort, genes by samples.
|
|
3
|
+
*
|
|
4
|
+
* The canonical design is the one of cBioPortal, ComplexHeatmap, and maftools. Each cell draws a gray ground,
|
|
5
|
+
* and an altered cell adds one glyph in the color of its class. A bar over the matrix draws the alteration
|
|
6
|
+
* count of each sample, stacked by class, and a bar at the right draws the share of altered samples of each
|
|
7
|
+
* gene with its percent text. A cohort holds too many samples for their names, thus the names hide and the
|
|
8
|
+
* axis title states the count.
|
|
9
|
+
*
|
|
10
|
+
* The figure computes two named summaries, and each one reads the rows of the bound table alone:
|
|
11
|
+
*
|
|
12
|
+
* - The alteration count of a sample: the count of its rows with a class, split by class.
|
|
13
|
+
* - The share of a gene: the count of samples with a class in that gene, over the count of every sample of
|
|
14
|
+
* the table. A row with an empty class names a sample with no alteration, thus that sample counts in the
|
|
15
|
+
* denominator and draws the ground alone.
|
|
16
|
+
*
|
|
17
|
+
* Each gene name and each percent text prints. Thus a figure of more genes than the default body holds at one
|
|
18
|
+
* line of text each states a taller body, and the grids keep their shares of it. A taller body grows the
|
|
19
|
+
* column exports in the same ratio.
|
|
20
|
+
*/
|
|
21
|
+
import type { ChartRow } from "../chart.js";
|
|
22
|
+
import type { FigureModule } from "./index.js";
|
|
23
|
+
/**
|
|
24
|
+
* The color of each common alteration class of the MAF standard, in the order of the legend.
|
|
25
|
+
*
|
|
26
|
+
* The first seven classes take the Okabe-Ito hues, and `Multi_Hit` takes its black, as maftools draws it. The
|
|
27
|
+
* two rare classes take the first two hues of the wide palette past the Okabe-Ito set.
|
|
28
|
+
*/
|
|
29
|
+
export declare const ALTERATION_CLASS_COLORS: Readonly<Record<string, string>>;
|
|
30
|
+
/** The gray ground of each cell. It is lighter than the muted color, thus a glyph of any class reads on it. */
|
|
31
|
+
export declare const CELL_GROUND_COLOR = "#e0e0e0";
|
|
32
|
+
/** The height of one gene row on the page, in pixels: one line of the page text with a gap. */
|
|
33
|
+
export declare const ONCOPRINT_ROW_PX = 16;
|
|
34
|
+
/** The oncoprint module: the sample, the gene, the class, and the annotation tracks. */
|
|
35
|
+
export declare const ONCOPRINT_FIGURE: FigureModule;
|
|
36
|
+
/** The class of one row, or `undefined` for a row that names a sample with no alteration. */
|
|
37
|
+
export declare function classOf(row: ChartRow, column: string): string | undefined;
|
|
38
|
+
/** The classes of the table in the order of the legend: the common classes in their fixed order, then each other class as it first appears. */
|
|
39
|
+
export declare function legendClasses(rows: readonly ChartRow[], column: string): string[];
|
|
40
|
+
/** The color of each class in legend order. An unknown class takes the next color of its own list, and past that list the muted color. */
|
|
41
|
+
export declare function classColors(classes: readonly string[]): string[];
|