@inflexa-ai/harness 0.42.2 → 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/sandbox/shared.d.ts +1 -6
- package/dist/agents/sandbox/shared.js +0 -15
- 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/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/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/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,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The lollipop: the mutations of one protein along its amino-acid positions, over its domain track.
|
|
3
|
+
*
|
|
4
|
+
* The canonical design is the one of maftools and of the cBioPortal mutation view. Each row draws a stem from
|
|
5
|
+
* zero to its count and a head at the count, in the color of its class. The domains of the track draw as
|
|
6
|
+
* labeled boxes on a band under the axis, over a gray backbone of the whole protein. The x axis spans the
|
|
7
|
+
* protein: from zero to the length column of the track, or to the largest end and position where the track
|
|
8
|
+
* names no length or where one of them passes it. The three largest counts carry the text of the label column.
|
|
9
|
+
*
|
|
10
|
+
* The figure computes no summary. Each stem is one row with a class where the block names a class column, each
|
|
11
|
+
* box is one row of the track, and the lane of a box is the first lane where it overlaps no earlier box, thus
|
|
12
|
+
* the lanes follow the order of the track rows.
|
|
13
|
+
*/
|
|
14
|
+
import type { FigureModule } from "./index.js";
|
|
15
|
+
/** The lollipop module: the position, the count, the class, the label, and the domain track. */
|
|
16
|
+
export declare const LOLLIPOP_FIGURE: FigureModule;
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The lollipop: the mutations of one protein along its amino-acid positions, over its domain track.
|
|
3
|
+
*
|
|
4
|
+
* The canonical design is the one of maftools and of the cBioPortal mutation view. Each row draws a stem from
|
|
5
|
+
* zero to its count and a head at the count, in the color of its class. The domains of the track draw as
|
|
6
|
+
* labeled boxes on a band under the axis, over a gray backbone of the whole protein. The x axis spans the
|
|
7
|
+
* protein: from zero to the length column of the track, or to the largest end and position where the track
|
|
8
|
+
* names no length or where one of them passes it. The three largest counts carry the text of the label column.
|
|
9
|
+
*
|
|
10
|
+
* The figure computes no summary. Each stem is one row with a class where the block names a class column, each
|
|
11
|
+
* box is one row of the track, and the lane of a box is the first lane where it overlaps no earlier box, thus
|
|
12
|
+
* the lanes follow the order of the track rows.
|
|
13
|
+
*/
|
|
14
|
+
import { err, ok } from "neverthrow";
|
|
15
|
+
import { channelColumn, channelOrder, channelTransform } from "../../contracts/report-blocks.js";
|
|
16
|
+
import { STEM_RENDERER } from "../chart-renderers.js";
|
|
17
|
+
import { CHART_INK, CHART_PALETTE, GUIDE_LINE_COLOR } from "../design.js";
|
|
18
|
+
import { categoricalPalette, categoryName, chartProblem, columnPresent, firstAppearance, firstFreeLanes, toNumber, valueAxis, valueAxisTitle, } from "./common.js";
|
|
19
|
+
import { classColors, classOf, legendClasses } from "./oncoprint.js";
|
|
20
|
+
/** The semantic title of the position axis and of the count axis. */
|
|
21
|
+
const POSITION_TITLE = "Amino-acid position";
|
|
22
|
+
const COUNT_TITLE = "Mutations";
|
|
23
|
+
/** The count of the largest counts that carry the text of the label column. */
|
|
24
|
+
const LABELED_COUNT = 3;
|
|
25
|
+
/**
|
|
26
|
+
* The count of characters that the full span of the x axis holds at the single journal column. A box label
|
|
27
|
+
* takes the share of this count that its box spans, thus a label never runs past its box at that width.
|
|
28
|
+
*/
|
|
29
|
+
const LABEL_CHARS_ACROSS = 60;
|
|
30
|
+
/** The fewest characters that a shortened box label shows. A box too narrow for them draws no label. */
|
|
31
|
+
const LABEL_MIN_CHARS = 4;
|
|
32
|
+
/** The places of the two grids with a track, in percent of the chart: the stems, and the domain band under them. */
|
|
33
|
+
const STEM_GRID = { left: "10%", right: "5%", top: "10%", bottom: "44%" };
|
|
34
|
+
const BAND_GRID = { left: "10%", right: "5%", top: "58%", height: "12%" };
|
|
35
|
+
/** The icon size and the gap of the class legend, in pixels, as the oncoprint states them. */
|
|
36
|
+
const LEGEND_ICON_PX = 10;
|
|
37
|
+
const LEGEND_GAP_PX = 8;
|
|
38
|
+
/** The margin of a box inside its lane, and of the backbone inside the band, in lanes. */
|
|
39
|
+
const BOX_MARGIN = 0.1;
|
|
40
|
+
const BACKBONE_MARGIN = 0.3;
|
|
41
|
+
/** The opacity of a domain box. The box reads as a region of the backbone, and its label reads on it. */
|
|
42
|
+
const BOX_OPACITY = 0.35;
|
|
43
|
+
/** The lollipop module: the position, the count, the class, the label, and the domain track. */
|
|
44
|
+
export const LOLLIPOP_FIGURE = { reads: new Set(["x", "y", "group", "label", "track"]), derive: deriveLollipop };
|
|
45
|
+
function deriveLollipop(block, rows, context) {
|
|
46
|
+
const position = numericChannel(block, rows, context, "x");
|
|
47
|
+
if (position.isErr())
|
|
48
|
+
return err(position.error);
|
|
49
|
+
const count = numericChannel(block, rows, context, "y");
|
|
50
|
+
if (count.isErr())
|
|
51
|
+
return err(count.error);
|
|
52
|
+
const groupChannel = block.encoding?.group;
|
|
53
|
+
if (groupChannel !== undefined && (channelTransform(groupChannel) !== undefined || channelOrder(groupChannel) !== undefined)) {
|
|
54
|
+
return err(chartProblem(context.blockId, 'The lollipop reads the "group" column as the class of each row, thus the channel takes no transform and no "orderBy".'));
|
|
55
|
+
}
|
|
56
|
+
const group = groupChannel === undefined ? undefined : channelColumn(groupChannel);
|
|
57
|
+
const label = block.encoding?.label;
|
|
58
|
+
for (const column of [group, label]) {
|
|
59
|
+
if (column !== undefined && rows.length > 0 && !columnPresent(column, rows, context.columns)) {
|
|
60
|
+
return err(chartProblem(context.blockId, `The column "${column}" is absent from every row.`));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const stems = [];
|
|
64
|
+
for (const row of rows) {
|
|
65
|
+
const x = toNumber(row[position.value]);
|
|
66
|
+
const y = toNumber(row[count.value]);
|
|
67
|
+
// A row with no class falls in no stem series, thus it takes no label and no room on the axis either.
|
|
68
|
+
if (x !== null && y !== null && (group === undefined || classOf(row, group) !== undefined))
|
|
69
|
+
stems.push({ row, x, y });
|
|
70
|
+
}
|
|
71
|
+
const boxes = context.track === undefined ? ok(undefined) : domainBoxes(context.blockId, context.track);
|
|
72
|
+
if (boxes.isErr())
|
|
73
|
+
return err(boxes.error);
|
|
74
|
+
const length = axisLength(stems, context.track, boxes.value);
|
|
75
|
+
const classes = group === undefined ? [] : legendClasses(rows, group);
|
|
76
|
+
const colors = classColors(classes);
|
|
77
|
+
const stemTitle = valueAxisTitle(context.labels, count.value, COUNT_TITLE);
|
|
78
|
+
const series = group === undefined
|
|
79
|
+
? [stemSeries(stemTitle, CHART_PALETTE[0], stems, label)]
|
|
80
|
+
: classes.map((name, index) => stemSeries(categoryName(name), colors[index], stems.filter((stem) => classOf(stem.row, group) === name), label));
|
|
81
|
+
if (label !== undefined)
|
|
82
|
+
series.push(labelSeries(stems, label));
|
|
83
|
+
const positionTitle = valueAxisTitle(context.labels, position.value, POSITION_TITLE);
|
|
84
|
+
const countAxis = { ...valueAxis("y", stemTitle, { min: 0 }), minInterval: 1 };
|
|
85
|
+
const legend = {
|
|
86
|
+
bottom: 0,
|
|
87
|
+
itemWidth: LEGEND_ICON_PX,
|
|
88
|
+
itemHeight: LEGEND_ICON_PX,
|
|
89
|
+
itemGap: LEGEND_GAP_PX,
|
|
90
|
+
data: series.filter((entry) => entry.renderItem === STEM_RENDERER).map((entry) => String(entry.name)),
|
|
91
|
+
};
|
|
92
|
+
if (boxes.value === undefined) {
|
|
93
|
+
return ok({ tooltip: { trigger: "item" }, xAxis: valueAxis("x", positionTitle, { min: 0, max: length }), yAxis: countAxis, series, legend });
|
|
94
|
+
}
|
|
95
|
+
const lanes = boxes.value.reduce((most, box) => Math.max(most, box.lane + 1), 1);
|
|
96
|
+
return ok({
|
|
97
|
+
tooltip: { trigger: "item" },
|
|
98
|
+
grid: [{ ...STEM_GRID }, { ...BAND_GRID }],
|
|
99
|
+
xAxis: [
|
|
100
|
+
{ type: "value", min: 0, max: length, axisLabel: { show: false }, axisTick: { show: false } },
|
|
101
|
+
{ ...valueAxis("x", positionTitle, { min: 0, max: length }), gridIndex: 1, axisLine: { show: false }, splitLine: { show: false } },
|
|
102
|
+
],
|
|
103
|
+
yAxis: [countAxis, { type: "value", gridIndex: 1, min: 0, max: lanes, inverse: true, show: false }],
|
|
104
|
+
series: [...series, bandSeries(boxes.value, length, lanes)],
|
|
105
|
+
legend,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/** The column of one numeric channel. An absent channel, a transform, and a column that no row holds each refuse. */
|
|
109
|
+
function numericChannel(block, rows, context, name) {
|
|
110
|
+
const channel = block.encoding?.[name];
|
|
111
|
+
if (channel === undefined) {
|
|
112
|
+
return err(chartProblem(context.blockId, `The lollipop figure needs a column for the "${name}" channel.`));
|
|
113
|
+
}
|
|
114
|
+
if (channelTransform(channel) !== undefined || channelOrder(channel) !== undefined) {
|
|
115
|
+
return err(chartProblem(context.blockId, `The lollipop reads the "${name}" column as it stands, thus the channel takes no transform and no "orderBy".`));
|
|
116
|
+
}
|
|
117
|
+
const column = channelColumn(channel);
|
|
118
|
+
if (rows.length > 0 && !columnPresent(column, rows, context.columns)) {
|
|
119
|
+
return err(chartProblem(context.blockId, `The column "${column}" is absent from every row.`));
|
|
120
|
+
}
|
|
121
|
+
return ok(column);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The boxes of the track in the order of its rows, each one in the first lane where it overlaps no earlier box.
|
|
125
|
+
* A row whose start or end is not numeric draws no box, and a row whose end precedes its start refuses.
|
|
126
|
+
*/
|
|
127
|
+
function domainBoxes(blockId, track) {
|
|
128
|
+
for (const column of [track.start, track.end, track.label]) {
|
|
129
|
+
if (track.rows.length > 0 && !columnPresent(column, track.rows, track.columns)) {
|
|
130
|
+
return err(chartProblem(blockId, `The track table holds no "${column}" column.`));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const laneOf = firstFreeLanes(track.rows.length);
|
|
134
|
+
const boxes = [];
|
|
135
|
+
for (const [index, row] of track.rows.entries()) {
|
|
136
|
+
const start = toNumber(row[track.start]);
|
|
137
|
+
const end = toNumber(row[track.end]);
|
|
138
|
+
if (start === null || end === null)
|
|
139
|
+
continue;
|
|
140
|
+
if (end < start) {
|
|
141
|
+
return err(chartProblem(blockId, `The track row ${index + 1} ends at ${end}, before its start at ${start}. A domain ends at or after its start.`));
|
|
142
|
+
}
|
|
143
|
+
boxes.push({ start, end, name: String(row[track.label] ?? ""), lane: laneOf(start, end) });
|
|
144
|
+
}
|
|
145
|
+
return ok(boxes);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* The end of the x axis: the largest of the length column of the first track row that holds a number, the
|
|
149
|
+
* largest end of a box, and the largest position of a stem. A mutation table and a domain table of two isoforms
|
|
150
|
+
* disagree on the length, and each stem and each box stays inside the plot.
|
|
151
|
+
*/
|
|
152
|
+
function axisLength(stems, track, boxes) {
|
|
153
|
+
let largest = 0;
|
|
154
|
+
const lengthColumn = track?.length;
|
|
155
|
+
if (track !== undefined && lengthColumn !== undefined) {
|
|
156
|
+
for (const row of track.rows) {
|
|
157
|
+
const length = toNumber(row[lengthColumn]);
|
|
158
|
+
if (length === null)
|
|
159
|
+
continue;
|
|
160
|
+
largest = length;
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
for (const stem of stems)
|
|
165
|
+
largest = Math.max(largest, stem.x);
|
|
166
|
+
for (const box of boxes ?? [])
|
|
167
|
+
largest = Math.max(largest, box.end);
|
|
168
|
+
return largest;
|
|
169
|
+
}
|
|
170
|
+
/** The stems in order of their count, the largest first, thus a smaller head draws over the stem of a larger one. */
|
|
171
|
+
function byCount(stems) {
|
|
172
|
+
return stems
|
|
173
|
+
.map((stem, place) => ({ stem, place }))
|
|
174
|
+
.sort((a, b) => b.stem.y - a.stem.y || a.place - b.place)
|
|
175
|
+
.map((entry) => entry.stem);
|
|
176
|
+
}
|
|
177
|
+
/** One series of stems through the stem renderer, in one color. Each item names its row by the label column. */
|
|
178
|
+
function stemSeries(name, color, stems, label) {
|
|
179
|
+
return {
|
|
180
|
+
type: "custom",
|
|
181
|
+
name,
|
|
182
|
+
renderItem: STEM_RENDERER,
|
|
183
|
+
encode: { x: 0, y: 1 },
|
|
184
|
+
itemStyle: { color },
|
|
185
|
+
tooltip: { formatter: "{a}<br/>{b}" },
|
|
186
|
+
data: byCount(stems).map((stem) => ({ name: label === undefined ? "" : String(stem.row[label] ?? ""), value: [stem.x, stem.y] })),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/** The labels of the three largest counts: one point with no symbol at each head, and its text above it. */
|
|
190
|
+
function labelSeries(stems, label) {
|
|
191
|
+
return {
|
|
192
|
+
type: "scatter",
|
|
193
|
+
silent: true,
|
|
194
|
+
symbolSize: 0,
|
|
195
|
+
z: 10,
|
|
196
|
+
label: { show: true, formatter: "{b}", position: "top", distance: 6, color: CHART_INK },
|
|
197
|
+
labelLayout: { hideOverlap: true },
|
|
198
|
+
data: byCount(stems)
|
|
199
|
+
.slice(0, LABELED_COUNT)
|
|
200
|
+
.map((stem) => ({ name: String(stem.row[label] ?? ""), value: [stem.x, stem.y] })),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* The band of the track: one gray backbone over the whole protein, then one box for each domain in its lane.
|
|
205
|
+
* The boxes of one name share one color. Each label takes the share of the axis that its box spans.
|
|
206
|
+
*/
|
|
207
|
+
function bandSeries(boxes, length, lanes) {
|
|
208
|
+
const names = firstAppearance(boxes.map((box) => box.name));
|
|
209
|
+
const palette = categoricalPalette(names.length);
|
|
210
|
+
const colorOf = new Map(names.map((name, place) => [name, palette[place % palette.length]]));
|
|
211
|
+
const areas = [
|
|
212
|
+
[
|
|
213
|
+
{ xAxis: 0, yAxis: BACKBONE_MARGIN, itemStyle: { color: GUIDE_LINE_COLOR, opacity: BOX_OPACITY }, label: { show: false } },
|
|
214
|
+
{ xAxis: length, yAxis: lanes - BACKBONE_MARGIN },
|
|
215
|
+
],
|
|
216
|
+
];
|
|
217
|
+
for (const box of boxes) {
|
|
218
|
+
const text = boxLabel(box, length);
|
|
219
|
+
areas.push([
|
|
220
|
+
{
|
|
221
|
+
name: box.name,
|
|
222
|
+
xAxis: box.start,
|
|
223
|
+
yAxis: box.lane + BOX_MARGIN,
|
|
224
|
+
itemStyle: { color: colorOf.get(box.name), opacity: BOX_OPACITY, borderColor: colorOf.get(box.name), borderWidth: 1 },
|
|
225
|
+
label: text === undefined ? { show: false } : { show: true, position: "inside", formatter: text, color: CHART_INK },
|
|
226
|
+
},
|
|
227
|
+
{ xAxis: box.end, yAxis: box.lane + 1 - BOX_MARGIN },
|
|
228
|
+
]);
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
type: "line",
|
|
232
|
+
xAxisIndex: 1,
|
|
233
|
+
yAxisIndex: 1,
|
|
234
|
+
silent: true,
|
|
235
|
+
data: [],
|
|
236
|
+
markArea: { silent: false, tooltip: { formatter: "{b}" }, data: areas },
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
/** The label of one box: the whole name where it fits the box, a shortened name where some of it fits, else none. */
|
|
240
|
+
function boxLabel(box, length) {
|
|
241
|
+
const room = length > 0 ? Math.round(((box.end - box.start) / length) * LABEL_CHARS_ACROSS) : 0;
|
|
242
|
+
if (box.name.length <= room)
|
|
243
|
+
return box.name;
|
|
244
|
+
if (room < LABEL_MIN_CHARS)
|
|
245
|
+
return undefined;
|
|
246
|
+
return `${box.name.slice(0, room - 1).trimEnd()}…`;
|
|
247
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The MA figure, as the `plotMA` of DESeq2 draws it: the mean of the normalized counts on a log axis, the
|
|
3
|
+
* log fold change on y, and the significant genes in blue over the gray genes that state no finding.
|
|
4
|
+
*
|
|
5
|
+
* - The x axis is logarithmic, and it ends at the decades around the positive means. A row whose mean is not
|
|
6
|
+
* positive has no place on it, and it draws no point.
|
|
7
|
+
* - The `p` column splits the rows at the significance cut: the declared thresholds, or the DESeq2 default
|
|
8
|
+
* of 0.1. A row with no p draws gray, as `plotMA` draws it. The significant points take the blue of the
|
|
9
|
+
* palette, which is the default of DESeq2, and the gray points draw under them.
|
|
10
|
+
* - A line sits at zero, and the y axis is symmetric around zero.
|
|
11
|
+
*
|
|
12
|
+
* The points come from the composition machinery, thus a table of many thousand genes reads the shared
|
|
13
|
+
* payload. The rule of the split rides the payload as plain data, thus the page splits the rows as the server
|
|
14
|
+
* does.
|
|
15
|
+
*/
|
|
16
|
+
import type { FigureModule } from "./index.js";
|
|
17
|
+
/** The significance cut of an MA plot where the block declares none: the `alpha` default of DESeq2. */
|
|
18
|
+
export declare const MA_P_THRESHOLD = 0.1;
|
|
19
|
+
/** The MA figure module. */
|
|
20
|
+
export declare const MA_FIGURE: FigureModule;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The MA figure, as the `plotMA` of DESeq2 draws it: the mean of the normalized counts on a log axis, the
|
|
3
|
+
* log fold change on y, and the significant genes in blue over the gray genes that state no finding.
|
|
4
|
+
*
|
|
5
|
+
* - The x axis is logarithmic, and it ends at the decades around the positive means. A row whose mean is not
|
|
6
|
+
* positive has no place on it, and it draws no point.
|
|
7
|
+
* - The `p` column splits the rows at the significance cut: the declared thresholds, or the DESeq2 default
|
|
8
|
+
* of 0.1. A row with no p draws gray, as `plotMA` draws it. The significant points take the blue of the
|
|
9
|
+
* palette, which is the default of DESeq2, and the gray points draw under them.
|
|
10
|
+
* - A line sits at zero, and the y axis is symmetric around zero.
|
|
11
|
+
*
|
|
12
|
+
* The points come from the composition machinery, thus a table of many thousand genes reads the shared
|
|
13
|
+
* payload. The rule of the split rides the payload as plain data, thus the page splits the rows as the server
|
|
14
|
+
* does.
|
|
15
|
+
*/
|
|
16
|
+
import { err, ok } from "neverthrow";
|
|
17
|
+
import { channelTransform } from "../../contracts/report-blocks.js";
|
|
18
|
+
import { MA_BASELINE } from "../chart-presets.js";
|
|
19
|
+
import { CHART_PALETTE, MUTED_CHART_COLOR } from "../design.js";
|
|
20
|
+
import { formatNumberCell } from "../number-format.js";
|
|
21
|
+
import { chartProblem, plainColumn, toNumber, valueAxisTitle } from "./common.js";
|
|
22
|
+
import { axisOf, DENSE_NULL_OPACITY, DENSE_NULL_SYMBOL_PX, DENSE_NULL_Z, DENSE_SIGNAL_OPACITY, DENSE_SIGNAL_SYMBOL_PX, demandedChannel, largest, niceCeiling, plottedValues, pointLayer, seriesOf, smallest, symmetricRange, } from "./dense.js";
|
|
23
|
+
/** The significance cut of an MA plot where the block declares none: the `alpha` default of DESeq2. */
|
|
24
|
+
export const MA_P_THRESHOLD = 0.1;
|
|
25
|
+
/** The name of the gray category. The muted rule of the renderer reads the same text. */
|
|
26
|
+
const MA_NULL = "Not significant";
|
|
27
|
+
/** The members of an MA block that the figure reads. */
|
|
28
|
+
const READS = new Set(["x", "y", "p", "label"]);
|
|
29
|
+
/**
|
|
30
|
+
* The two-way split of an MA plot, one row at a time: under the cut of the p column, or not.
|
|
31
|
+
*
|
|
32
|
+
* The significant name states the column and the cut, thus the legend states the rule of the colors. A row
|
|
33
|
+
* whose mean is not positive belongs to no category, because the log axis has no place for it.
|
|
34
|
+
*/
|
|
35
|
+
function maClassification(rows, pColumn, pTitle, cut) {
|
|
36
|
+
const significant = `${pTitle} < ${formatNumberCell(cut, "compact-scientific").text}`;
|
|
37
|
+
return {
|
|
38
|
+
categories: [
|
|
39
|
+
{ name: significant, muted: false },
|
|
40
|
+
{ name: MA_NULL, muted: true },
|
|
41
|
+
],
|
|
42
|
+
rule: { kind: "ma", column: pColumn, cut },
|
|
43
|
+
categoryOf: (x, y, index) => {
|
|
44
|
+
if (x === null || y === null || x <= 0)
|
|
45
|
+
return undefined;
|
|
46
|
+
const p = toNumber(rows[index][pColumn]);
|
|
47
|
+
return p !== null && p < cut ? significant : MA_NULL;
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** The decade at or under a positive value, and the decade at or over it. */
|
|
52
|
+
function decadeFloor(value) {
|
|
53
|
+
return Math.pow(10, Math.floor(Math.log10(value)));
|
|
54
|
+
}
|
|
55
|
+
function decadeCeiling(value) {
|
|
56
|
+
return Math.pow(10, Math.ceil(Math.log10(value)));
|
|
57
|
+
}
|
|
58
|
+
function deriveMa(block, rows, context) {
|
|
59
|
+
const encoding = block.encoding ?? {};
|
|
60
|
+
if (block.thresholds?.effect !== undefined) {
|
|
61
|
+
// The figure splits its rows by the p column alone, thus an effect cut would move nothing.
|
|
62
|
+
return err(chartProblem(context.blockId, "The ma chart reads the significance cut alone. Omit the effect cut."));
|
|
63
|
+
}
|
|
64
|
+
const x = demandedChannel(context.blockId, "ma", encoding, "x");
|
|
65
|
+
if (x.isErr())
|
|
66
|
+
return err(x.error);
|
|
67
|
+
if (channelTransform(x.value) !== undefined) {
|
|
68
|
+
return err(chartProblem(context.blockId, 'The ma figure draws the "x" channel on a log axis, thus it takes no transform.'));
|
|
69
|
+
}
|
|
70
|
+
const y = demandedChannel(context.blockId, "ma", encoding, "y");
|
|
71
|
+
if (y.isErr())
|
|
72
|
+
return err(y.error);
|
|
73
|
+
const pColumn = plainColumn("ma", encoding, "p", { blockId: context.blockId, rows, columns: context.columns });
|
|
74
|
+
if (pColumn.isErr())
|
|
75
|
+
return err(pColumn.error);
|
|
76
|
+
const composition = {
|
|
77
|
+
series: [{ form: "scatter", encoding: { x: x.value, y: y.value, ...(encoding.label !== undefined ? { label: encoding.label } : {}) } }],
|
|
78
|
+
annotations: [{ kind: "reference-line", axis: "y", value: MA_BASELINE }],
|
|
79
|
+
axes: { x: { scale: "log" } },
|
|
80
|
+
};
|
|
81
|
+
const cut = block.thresholds?.significance ?? MA_P_THRESHOLD;
|
|
82
|
+
const classification = pColumn.value === undefined ? undefined : maClassification(rows, pColumn.value, valueAxisTitle(context.labels, pColumn.value), cut);
|
|
83
|
+
const composed = context.compose(composition, classification !== undefined ? { classification } : {});
|
|
84
|
+
if (composed.isErr())
|
|
85
|
+
return err(composed.error);
|
|
86
|
+
const xs = plottedValues(rows, x.value);
|
|
87
|
+
const ys = plottedValues(rows, y.value);
|
|
88
|
+
const means = [];
|
|
89
|
+
const effects = [];
|
|
90
|
+
for (let index = 0; index < rows.length; index += 1) {
|
|
91
|
+
const mean = xs[index];
|
|
92
|
+
const effect = ys[index];
|
|
93
|
+
if (mean === null || effect === null || mean <= 0)
|
|
94
|
+
continue;
|
|
95
|
+
means.push(mean);
|
|
96
|
+
effects.push(Math.abs(effect));
|
|
97
|
+
}
|
|
98
|
+
const low = smallest(means);
|
|
99
|
+
const high = largest(means);
|
|
100
|
+
const reach = niceCeiling(largest(effects) ?? 0);
|
|
101
|
+
const option = composed.value;
|
|
102
|
+
return ok({
|
|
103
|
+
...option,
|
|
104
|
+
xAxis: { ...axisOf(option, "xAxis"), ...(low !== undefined && high !== undefined ? { min: decadeFloor(low), max: decadeCeiling(high) } : {}) },
|
|
105
|
+
yAxis: { ...axisOf(option, "yAxis"), ...symmetricRange(reach) },
|
|
106
|
+
series: classification === undefined
|
|
107
|
+
? seriesOf(option).map((series) => ({ ...series, symbolSize: DENSE_SIGNAL_SYMBOL_PX }))
|
|
108
|
+
: seriesOf(option).map((series, place) => place === 0
|
|
109
|
+
? { ...series, ...pointLayer(CHART_PALETTE[0], DENSE_SIGNAL_SYMBOL_PX, DENSE_SIGNAL_OPACITY) }
|
|
110
|
+
: { ...series, ...pointLayer(MUTED_CHART_COLOR, DENSE_NULL_SYMBOL_PX, DENSE_NULL_OPACITY, DENSE_NULL_Z) }),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/** The MA figure module. */
|
|
114
|
+
export const MA_FIGURE = { reads: READS, derive: deriveMa };
|
|
@@ -0,0 +1,60 @@
|
|
|
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 type { Cell } from "../chart.js";
|
|
31
|
+
import type { FigureModule } from "./index.js";
|
|
32
|
+
import { type Box, type NameFrame } from "./label-room.js";
|
|
33
|
+
/** The two colors that alternate between the chromosomes: the blue of the palette and the muted gray. */
|
|
34
|
+
export declare const MANHATTAN_COLORS: readonly ["#0072b2", "#94a3b8"];
|
|
35
|
+
/** The suggestive level of a GWAS, the second guide line of qqman. */
|
|
36
|
+
export declare const MANHATTAN_SUGGESTIVE_P = 0.00001;
|
|
37
|
+
/** The largest count of lead variants that show their name, over the whole genome. */
|
|
38
|
+
export declare const MANHATTAN_LABEL_COUNT = 10;
|
|
39
|
+
/**
|
|
40
|
+
* The smallest gap between two chromosome names that both print, as a share of the text size.
|
|
41
|
+
*
|
|
42
|
+
* The small chromosomes lie close together. The name of a chromosome whose text comes nearer than this gap to
|
|
43
|
+
* the text of the last printed name, in the plot of a render, does not print in that render.
|
|
44
|
+
*/
|
|
45
|
+
export declare const MANHATTAN_NAME_GAP_SHARE = 0.5;
|
|
46
|
+
/**
|
|
47
|
+
* The box in data units that the label of the genome-wide line takes at the right end of the line, over it, in
|
|
48
|
+
* the plot of one render.
|
|
49
|
+
*/
|
|
50
|
+
export declare function guideLabelBox(line: number, x: {
|
|
51
|
+
readonly min: number;
|
|
52
|
+
readonly max: number;
|
|
53
|
+
}, top: number, text: string, frame: NameFrame): Box;
|
|
54
|
+
/**
|
|
55
|
+
* The genome order of two chromosome names: the numbered chromosomes in numeric order, then X, Y, and MT, then
|
|
56
|
+
* each other name in text order. A `chr` prefix takes no part in the order.
|
|
57
|
+
*/
|
|
58
|
+
export declare function compareChromosomes(a: Cell, b: Cell): number;
|
|
59
|
+
/** The Manhattan figure module. */
|
|
60
|
+
export declare const MANHATTAN_FIGURE: FigureModule;
|