@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,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dendrogram of one category axis of a heatmap: the check of its edge table, the leaf order that the axis
|
|
3
|
+
* takes, and the elbow of each edge.
|
|
4
|
+
*
|
|
5
|
+
* The tree table holds one row for each edge: the parent node, the child node, and the height of the parent.
|
|
6
|
+
* A leaf is a child that is never a parent, and the root is the one parent that is never a child. The leaf
|
|
7
|
+
* order is the depth-first order from the root, with the children of a node in the order of the table. Thus
|
|
8
|
+
* the scipy `leaves_list` order and the R `hclust` order both survive, because each writes the left child
|
|
9
|
+
* first.
|
|
10
|
+
*
|
|
11
|
+
* A leaf sits at height zero and at its place in the leaf order. An inner node sits at its height, midway
|
|
12
|
+
* between the places of its outer children, as the scipy and the R dendrograms place it.
|
|
13
|
+
*/
|
|
14
|
+
import { type Result } from "neverthrow";
|
|
15
|
+
import type { Cell } from "../chart.js";
|
|
16
|
+
import type { RenderProblem } from "../types.js";
|
|
17
|
+
import type { FigureTree } from "./index.js";
|
|
18
|
+
/** One checked tree: the axis categories in leaf order, and the elbow of each edge in table order. */
|
|
19
|
+
export interface CheckedTree {
|
|
20
|
+
readonly order: readonly Cell[];
|
|
21
|
+
readonly height: number;
|
|
22
|
+
readonly elbows: readonly Elbow[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The elbow of one edge, in tree coordinates: the place along the axis and the height of each of its three
|
|
26
|
+
* points. It rises from the child to the height of the parent, then it runs across to the place of the parent.
|
|
27
|
+
*/
|
|
28
|
+
export type Elbow = readonly [readonly [number, number], readonly [number, number], readonly [number, number]];
|
|
29
|
+
/**
|
|
30
|
+
* Check the tree of one axis against the categories of that axis, and give the leaf order and the elbows.
|
|
31
|
+
*
|
|
32
|
+
* Each fault refuses, and the problem names the fault: an edge with an empty node, a height that is no
|
|
33
|
+
* number, a node with two heights or two parents, no root or two roots, a cycle, a child over its parent, a
|
|
34
|
+
* leaf that is no category, and a category that no leaf names.
|
|
35
|
+
*/
|
|
36
|
+
export declare function checkTree(blockId: string, axis: "x" | "y", tree: FigureTree, categories: readonly Cell[]): Result<CheckedTree, RenderProblem>;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dendrogram of one category axis of a heatmap: the check of its edge table, the leaf order that the axis
|
|
3
|
+
* takes, and the elbow of each edge.
|
|
4
|
+
*
|
|
5
|
+
* The tree table holds one row for each edge: the parent node, the child node, and the height of the parent.
|
|
6
|
+
* A leaf is a child that is never a parent, and the root is the one parent that is never a child. The leaf
|
|
7
|
+
* order is the depth-first order from the root, with the children of a node in the order of the table. Thus
|
|
8
|
+
* the scipy `leaves_list` order and the R `hclust` order both survive, because each writes the left child
|
|
9
|
+
* first.
|
|
10
|
+
*
|
|
11
|
+
* A leaf sits at height zero and at its place in the leaf order. An inner node sits at its height, midway
|
|
12
|
+
* between the places of its outer children, as the scipy and the R dendrograms place it.
|
|
13
|
+
*/
|
|
14
|
+
import { err, ok } from "neverthrow";
|
|
15
|
+
import { chartProblem, toNumber } from "./common.js";
|
|
16
|
+
/**
|
|
17
|
+
* Check the tree of one axis against the categories of that axis, and give the leaf order and the elbows.
|
|
18
|
+
*
|
|
19
|
+
* Each fault refuses, and the problem names the fault: an edge with an empty node, a height that is no
|
|
20
|
+
* number, a node with two heights or two parents, no root or two roots, a cycle, a child over its parent, a
|
|
21
|
+
* leaf that is no category, and a category that no leaf names.
|
|
22
|
+
*/
|
|
23
|
+
export function checkTree(blockId, axis, tree, categories) {
|
|
24
|
+
const fault = (detail) => err(chartProblem(blockId, `The tree of ${axis} ${detail}`));
|
|
25
|
+
if (tree.rows.length === 0)
|
|
26
|
+
return fault("holds no edge.");
|
|
27
|
+
const edges = [];
|
|
28
|
+
const heightOf = new Map();
|
|
29
|
+
const parentOf = new Map();
|
|
30
|
+
const childrenOf = new Map();
|
|
31
|
+
for (const [index, row] of tree.rows.entries()) {
|
|
32
|
+
const parent = nodeName(row[tree.parent]);
|
|
33
|
+
const child = nodeName(row[tree.child]);
|
|
34
|
+
if (parent === undefined || child === undefined) {
|
|
35
|
+
return fault(`holds an edge with no ${parent === undefined ? "parent" : "child"} at row ${index + 1}.`);
|
|
36
|
+
}
|
|
37
|
+
const height = toNumber(row[tree.height]);
|
|
38
|
+
if (height === null) {
|
|
39
|
+
return fault(`gives the node "${parent}" the height "${String(row[tree.height] ?? "")}", which is not a number.`);
|
|
40
|
+
}
|
|
41
|
+
const known = heightOf.get(parent);
|
|
42
|
+
if (known !== undefined && known !== height)
|
|
43
|
+
return fault(`gives the node "${parent}" two heights, ${known} and ${height}.`);
|
|
44
|
+
heightOf.set(parent, height);
|
|
45
|
+
const prior = parentOf.get(child);
|
|
46
|
+
if (prior === parent)
|
|
47
|
+
return fault(`holds the edge from "${parent}" to "${child}" two times.`);
|
|
48
|
+
if (prior !== undefined)
|
|
49
|
+
return fault(`gives the node "${child}" two parents, "${prior}" and "${parent}".`);
|
|
50
|
+
parentOf.set(child, parent);
|
|
51
|
+
const children = childrenOf.get(parent) ?? [];
|
|
52
|
+
children.push(child);
|
|
53
|
+
childrenOf.set(parent, children);
|
|
54
|
+
edges.push({ parent, child, height });
|
|
55
|
+
}
|
|
56
|
+
const roots = [...childrenOf.keys()].filter((node) => !parentOf.has(node));
|
|
57
|
+
if (roots.length === 0)
|
|
58
|
+
return fault(`holds a cycle through the node "${edges[0].parent}".`);
|
|
59
|
+
if (roots.length > 1)
|
|
60
|
+
return fault(`holds more than one root, for example "${roots[0]}" and "${roots[1]}". A tree holds one root.`);
|
|
61
|
+
// Each child has one parent and the root has none, thus a walk from the root meets each node one time. A
|
|
62
|
+
// node that the walk never meets sits on a cycle apart from the root.
|
|
63
|
+
const preorder = [];
|
|
64
|
+
const stack = [roots[0]];
|
|
65
|
+
for (let node = stack.pop(); node !== undefined; node = stack.pop()) {
|
|
66
|
+
preorder.push(node);
|
|
67
|
+
const children = childrenOf.get(node) ?? [];
|
|
68
|
+
for (let index = children.length - 1; index >= 0; index -= 1)
|
|
69
|
+
stack.push(children[index]);
|
|
70
|
+
}
|
|
71
|
+
const met = new Set(preorder);
|
|
72
|
+
const stray = edges.find((edge) => !met.has(edge.parent));
|
|
73
|
+
if (stray !== undefined)
|
|
74
|
+
return fault(`holds a cycle through the node "${stray.parent}".`);
|
|
75
|
+
for (const edge of edges) {
|
|
76
|
+
const own = heightOf.get(edge.child);
|
|
77
|
+
if (own !== undefined && own > edge.height) {
|
|
78
|
+
return fault(`puts the node "${edge.child}" at the height ${own}, over the height ${edge.height} of its parent "${edge.parent}".`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const byName = new Map();
|
|
82
|
+
for (const category of categories)
|
|
83
|
+
byName.set(String(category), category);
|
|
84
|
+
const leaves = preorder.filter((node) => !childrenOf.has(node));
|
|
85
|
+
const order = [];
|
|
86
|
+
for (const leaf of leaves) {
|
|
87
|
+
const category = byName.get(leaf);
|
|
88
|
+
if (category === undefined)
|
|
89
|
+
return fault(`holds the leaf "${leaf}", which is no category of the ${axis} axis.`);
|
|
90
|
+
order.push(category);
|
|
91
|
+
}
|
|
92
|
+
const named = new Set(leaves);
|
|
93
|
+
const absent = categories.find((category) => !named.has(String(category)));
|
|
94
|
+
if (absent !== undefined)
|
|
95
|
+
return fault(`holds no leaf for the ${axis} category "${String(absent)}".`);
|
|
96
|
+
const place = new Map(leaves.map((leaf, index) => [leaf, index]));
|
|
97
|
+
// The reverse of the preorder meets each child before its parent, and the walk met each node, thus each
|
|
98
|
+
// lookup below finds a place.
|
|
99
|
+
const placeOf = (node) => place.get(node);
|
|
100
|
+
for (let index = preorder.length - 1; index >= 0; index -= 1) {
|
|
101
|
+
const node = preorder[index];
|
|
102
|
+
const children = childrenOf.get(node);
|
|
103
|
+
if (children === undefined)
|
|
104
|
+
continue;
|
|
105
|
+
let low = Infinity;
|
|
106
|
+
let high = -Infinity;
|
|
107
|
+
for (const child of children) {
|
|
108
|
+
low = Math.min(low, placeOf(child));
|
|
109
|
+
high = Math.max(high, placeOf(child));
|
|
110
|
+
}
|
|
111
|
+
place.set(node, (low + high) / 2);
|
|
112
|
+
}
|
|
113
|
+
const heightAt = (node) => heightOf.get(node) ?? 0;
|
|
114
|
+
const elbows = edges.map((edge) => {
|
|
115
|
+
const child = placeOf(edge.child);
|
|
116
|
+
return [
|
|
117
|
+
[child, heightAt(edge.child)],
|
|
118
|
+
[child, edge.height],
|
|
119
|
+
[placeOf(edge.parent), edge.height],
|
|
120
|
+
];
|
|
121
|
+
});
|
|
122
|
+
return ok({ order, height: heightAt(roots[0]), elbows });
|
|
123
|
+
}
|
|
124
|
+
/** The name of one node cell, or `undefined` for an empty cell. */
|
|
125
|
+
function nodeName(cell) {
|
|
126
|
+
if (cell === undefined)
|
|
127
|
+
return undefined;
|
|
128
|
+
const name = String(cell);
|
|
129
|
+
return name === "" ? undefined : name;
|
|
130
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared parts of the dense scatter figures: the volcano, the MA plot, the Manhattan plot, and the QQ plot.
|
|
3
|
+
*
|
|
4
|
+
* Each of these figures draws one point for each row of a table of many thousand rows. Thus each one builds
|
|
5
|
+
* its points through the composition machinery of the context, and the points read the shared payload past
|
|
6
|
+
* the inline bound. The figure then styles the series of the composition output and sets its axes. It never
|
|
7
|
+
* puts a series before that output, because the page fills the leading series from their descriptors.
|
|
8
|
+
*/
|
|
9
|
+
import { type Result } from "neverthrow";
|
|
10
|
+
import { type ChartChannel, type ChartEncoding, type ChartType } from "../../contracts/report-blocks.js";
|
|
11
|
+
import type { ChartRow, EchartOption } from "../chart.js";
|
|
12
|
+
import type { RenderProblem } from "../types.js";
|
|
13
|
+
import { type LeaderName, type NamedPoint, type NameFrame } from "./label-room.js";
|
|
14
|
+
/**
|
|
15
|
+
* The symbol sizes in pixels of a dense figure: a signal point, and a point that states no finding.
|
|
16
|
+
*
|
|
17
|
+
* A dense cloud reads by its shape, and a large symbol fills the plot with ink. The null points draw smaller,
|
|
18
|
+
* thus the signal points read first.
|
|
19
|
+
*/
|
|
20
|
+
export declare const DENSE_SIGNAL_SYMBOL_PX = 5;
|
|
21
|
+
export declare const DENSE_NULL_SYMBOL_PX = 3;
|
|
22
|
+
/** The opacity of a signal point and of a null point. A null cloud of thousands of points recedes. */
|
|
23
|
+
export declare const DENSE_SIGNAL_OPACITY = 0.85;
|
|
24
|
+
export declare const DENSE_NULL_OPACITY = 0.45;
|
|
25
|
+
/**
|
|
26
|
+
* The drawing order of a null layer. The runtime draws a scatter at the order 2, thus a null layer at 1 draws
|
|
27
|
+
* under the signal points whatever its place in the series list.
|
|
28
|
+
*/
|
|
29
|
+
export declare const DENSE_NULL_Z = 1;
|
|
30
|
+
/**
|
|
31
|
+
* The symbol and the size in pixels of a point whose stored p is 0.
|
|
32
|
+
*
|
|
33
|
+
* A stored zero states that the true p sits under the resolution of the test, and `neg_log10` gives it no value.
|
|
34
|
+
* The figure draws such a point at the top of the plotted range, and the upward triangle tells it apart from a
|
|
35
|
+
* point at its true height.
|
|
36
|
+
*/
|
|
37
|
+
export declare const BELOW_RESOLUTION_SYMBOL = "triangle";
|
|
38
|
+
export declare const BELOW_RESOLUTION_SYMBOL_PX = 7;
|
|
39
|
+
/** The name and the tooltip of a series of stored zeros, where the figure gives the series no name of its own. */
|
|
40
|
+
export declare const BELOW_RESOLUTION_NAME = "p = 0";
|
|
41
|
+
export declare const BELOW_RESOLUTION_TOOLTIP: EchartOption;
|
|
42
|
+
/**
|
|
43
|
+
* The rows whose stored p is 0 and whose other coordinate draws. `others` gives the plotted value of the other
|
|
44
|
+
* axis for each row.
|
|
45
|
+
*/
|
|
46
|
+
export declare function belowResolutionRows(rows: readonly ChartRow[], pColumn: string, others: readonly (number | null)[]): number[];
|
|
47
|
+
/**
|
|
48
|
+
* The drawn height of a stored zero: the largest finite transformed p of the table, where it passes the line
|
|
49
|
+
* of the figure, else one unit over the line. Thus a stored zero always reads as significant.
|
|
50
|
+
*/
|
|
51
|
+
export declare function belowResolutionHeight(peak: number | undefined, line: number): number;
|
|
52
|
+
/** The name of the series that carries the point names of a dense figure. */
|
|
53
|
+
export declare const POINT_NAMES = "Point names";
|
|
54
|
+
/**
|
|
55
|
+
* The series of the point names of a dense figure: one point with no symbol at the anchor of each name, with the
|
|
56
|
+
* name as its label, and a leader line from each named point to its anchor. A name with no place draws nothing.
|
|
57
|
+
*
|
|
58
|
+
* The derivation places each name, because the chart runtime moves no label that sits on its point. The series
|
|
59
|
+
* follows the points in the series list and keeps its rows inline, thus the points still read the shared
|
|
60
|
+
* payload, and the names draw over them.
|
|
61
|
+
*
|
|
62
|
+
* The page frame reads a window 1280 pixels wide. On a narrower window the plot is smaller than its frame and the
|
|
63
|
+
* text keeps its size, thus the runtime also hides a name that overlaps another.
|
|
64
|
+
*/
|
|
65
|
+
export declare function pointNameSeries(names: readonly NamedPoint[], placed: readonly (LeaderName | undefined)[]): EchartOption[];
|
|
66
|
+
/**
|
|
67
|
+
* The series of the text that a figure places itself, for the page and for each export.
|
|
68
|
+
*
|
|
69
|
+
* `build` places the text in one frame and gives the series that carry it. The page takes the series of the page
|
|
70
|
+
* frame, and each export takes the series of the frame of its size, under its width in the member that
|
|
71
|
+
* `exportOption` reads. `names` are the names of the series that `build` can give, thus an export drops each page
|
|
72
|
+
* series of those names, and a size where no text finds a place draws none.
|
|
73
|
+
*/
|
|
74
|
+
export declare function sizedSeries(names: readonly string[], build: (frame: NameFrame) => EchartOption[]): {
|
|
75
|
+
readonly page: EchartOption[];
|
|
76
|
+
readonly member: EchartOption;
|
|
77
|
+
};
|
|
78
|
+
/** The channel of the quick path that a dense figure demands. An absent channel refuses. */
|
|
79
|
+
export declare function demandedChannel(blockId: string, chartType: ChartType, encoding: ChartEncoding, channel: "x" | "y" | "group"): Result<ChartChannel, RenderProblem>;
|
|
80
|
+
/**
|
|
81
|
+
* The plotted value of one channel for each row: the transformed value where the channel declares a
|
|
82
|
+
* transform, else the number of the cell. The composition reads the same numbers, thus a rule of the figure
|
|
83
|
+
* and a point of the chart agree.
|
|
84
|
+
*/
|
|
85
|
+
export declare function plottedValues(rows: readonly ChartRow[], channel: ChartChannel): (number | null)[];
|
|
86
|
+
/**
|
|
87
|
+
* The smallest round number at or above a positive value: one of the round coefficients times a power of ten.
|
|
88
|
+
* A value that is not positive gives one.
|
|
89
|
+
*
|
|
90
|
+
* A figure ends an axis here. The ladder is fine, thus the end leaves little empty room past the data.
|
|
91
|
+
*/
|
|
92
|
+
export declare function niceCeiling(value: number): number;
|
|
93
|
+
/**
|
|
94
|
+
* The range fields of an axis symmetric around zero, with a tick at each end, at zero, and at each half.
|
|
95
|
+
*
|
|
96
|
+
* The runtime picks its own tick step, and a step that does not divide the end leaves the last tick short of
|
|
97
|
+
* it. The half of the end always divides it, thus the two ends and zero carry a tick.
|
|
98
|
+
*/
|
|
99
|
+
export declare function symmetricRange(end: number): EchartOption;
|
|
100
|
+
/** The largest value of a list, or `undefined` for an empty list. The loop keeps a long list off the stack. */
|
|
101
|
+
export declare function largest(values: readonly number[]): number | undefined;
|
|
102
|
+
/** The smallest value of a list, or `undefined` for an empty list. */
|
|
103
|
+
export declare function smallest(values: readonly number[]): number | undefined;
|
|
104
|
+
/** The series of an option, as objects. */
|
|
105
|
+
export declare function seriesOf(option: EchartOption): EchartOption[];
|
|
106
|
+
/** The axis of an option as one object. A composition of one grid gives one object for each axis. */
|
|
107
|
+
export declare function axisOf(option: EchartOption, key: "xAxis" | "yAxis"): EchartOption;
|
|
108
|
+
/** The fields of one layer of points: the fill, the opacity, the symbol size, and the drawing order. */
|
|
109
|
+
export declare function pointLayer(color: string, size: number, opacity: number, z?: number): EchartOption;
|
|
110
|
+
/** The shown text of a count in a legend name: a whole number with a thousands separator. */
|
|
111
|
+
export declare function countText(count: number): string;
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared parts of the dense scatter figures: the volcano, the MA plot, the Manhattan plot, and the QQ plot.
|
|
3
|
+
*
|
|
4
|
+
* Each of these figures draws one point for each row of a table of many thousand rows. Thus each one builds
|
|
5
|
+
* its points through the composition machinery of the context, and the points read the shared payload past
|
|
6
|
+
* the inline bound. The figure then styles the series of the composition output and sets its axes. It never
|
|
7
|
+
* puts a series before that output, because the page fills the leading series from their descriptors.
|
|
8
|
+
*/
|
|
9
|
+
import { err, ok } from "neverthrow";
|
|
10
|
+
import { channelColumn, channelTransform } from "../../contracts/report-blocks.js";
|
|
11
|
+
import { SIZED_SERIES_MEMBER } from "../chart-renderers.js";
|
|
12
|
+
import { CHART_EXPORT_SIZES, CHART_INK } from "../design.js";
|
|
13
|
+
import { formatNumberCell } from "../number-format.js";
|
|
14
|
+
import { chartProblem, toNumber, transformColumn } from "./common.js";
|
|
15
|
+
import { EXPORT_PLOT_FRAMES, PAGE_PLOT_FRAME } from "./label-room.js";
|
|
16
|
+
/**
|
|
17
|
+
* The symbol sizes in pixels of a dense figure: a signal point, and a point that states no finding.
|
|
18
|
+
*
|
|
19
|
+
* A dense cloud reads by its shape, and a large symbol fills the plot with ink. The null points draw smaller,
|
|
20
|
+
* thus the signal points read first.
|
|
21
|
+
*/
|
|
22
|
+
export const DENSE_SIGNAL_SYMBOL_PX = 5;
|
|
23
|
+
export const DENSE_NULL_SYMBOL_PX = 3;
|
|
24
|
+
/** The opacity of a signal point and of a null point. A null cloud of thousands of points recedes. */
|
|
25
|
+
export const DENSE_SIGNAL_OPACITY = 0.85;
|
|
26
|
+
export const DENSE_NULL_OPACITY = 0.45;
|
|
27
|
+
/**
|
|
28
|
+
* The drawing order of a null layer. The runtime draws a scatter at the order 2, thus a null layer at 1 draws
|
|
29
|
+
* under the signal points whatever its place in the series list.
|
|
30
|
+
*/
|
|
31
|
+
export const DENSE_NULL_Z = 1;
|
|
32
|
+
/**
|
|
33
|
+
* The symbol and the size in pixels of a point whose stored p is 0.
|
|
34
|
+
*
|
|
35
|
+
* A stored zero states that the true p sits under the resolution of the test, and `neg_log10` gives it no value.
|
|
36
|
+
* The figure draws such a point at the top of the plotted range, and the upward triangle tells it apart from a
|
|
37
|
+
* point at its true height.
|
|
38
|
+
*/
|
|
39
|
+
export const BELOW_RESOLUTION_SYMBOL = "triangle";
|
|
40
|
+
export const BELOW_RESOLUTION_SYMBOL_PX = 7;
|
|
41
|
+
/** The name and the tooltip of a series of stored zeros, where the figure gives the series no name of its own. */
|
|
42
|
+
export const BELOW_RESOLUTION_NAME = "p = 0";
|
|
43
|
+
export const BELOW_RESOLUTION_TOOLTIP = { formatter: "{b}<br/>{a}: p = 0" };
|
|
44
|
+
/**
|
|
45
|
+
* The rows whose stored p is 0 and whose other coordinate draws. `others` gives the plotted value of the other
|
|
46
|
+
* axis for each row.
|
|
47
|
+
*/
|
|
48
|
+
export function belowResolutionRows(rows, pColumn, others) {
|
|
49
|
+
const found = [];
|
|
50
|
+
for (let index = 0; index < rows.length; index += 1) {
|
|
51
|
+
if (toNumber(rows[index][pColumn]) === 0 && others[index] !== null)
|
|
52
|
+
found.push(index);
|
|
53
|
+
}
|
|
54
|
+
return found;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The drawn height of a stored zero: the largest finite transformed p of the table, where it passes the line
|
|
58
|
+
* of the figure, else one unit over the line. Thus a stored zero always reads as significant.
|
|
59
|
+
*/
|
|
60
|
+
export function belowResolutionHeight(peak, line) {
|
|
61
|
+
return peak !== undefined && peak > line ? peak : line + 1;
|
|
62
|
+
}
|
|
63
|
+
/** The name of the series that carries the point names of a dense figure. */
|
|
64
|
+
export const POINT_NAMES = "Point names";
|
|
65
|
+
/** The stroke width in pixels of the leader line between a point and its name. */
|
|
66
|
+
const LEADER_LINE_WIDTH_PX = 0.5;
|
|
67
|
+
/**
|
|
68
|
+
* The series of the point names of a dense figure: one point with no symbol at the anchor of each name, with the
|
|
69
|
+
* name as its label, and a leader line from each named point to its anchor. A name with no place draws nothing.
|
|
70
|
+
*
|
|
71
|
+
* The derivation places each name, because the chart runtime moves no label that sits on its point. The series
|
|
72
|
+
* follows the points in the series list and keeps its rows inline, thus the points still read the shared
|
|
73
|
+
* payload, and the names draw over them.
|
|
74
|
+
*
|
|
75
|
+
* The page frame reads a window 1280 pixels wide. On a narrower window the plot is smaller than its frame and the
|
|
76
|
+
* text keeps its size, thus the runtime also hides a name that overlaps another.
|
|
77
|
+
*/
|
|
78
|
+
export function pointNameSeries(names, placed) {
|
|
79
|
+
const items = [];
|
|
80
|
+
const leaders = [];
|
|
81
|
+
for (const [index, name] of names.entries()) {
|
|
82
|
+
const anchor = placed[index];
|
|
83
|
+
if (anchor === undefined)
|
|
84
|
+
continue;
|
|
85
|
+
items.push({ name: name.text, value: [anchor.x, anchor.y], label: { position: anchor.side } });
|
|
86
|
+
leaders.push([{ coord: [name.x, name.y] }, { coord: [anchor.x, anchor.y] }]);
|
|
87
|
+
}
|
|
88
|
+
if (items.length === 0)
|
|
89
|
+
return [];
|
|
90
|
+
return [
|
|
91
|
+
{
|
|
92
|
+
type: "scatter",
|
|
93
|
+
name: POINT_NAMES,
|
|
94
|
+
silent: true,
|
|
95
|
+
symbolSize: 0,
|
|
96
|
+
tooltip: { show: false },
|
|
97
|
+
label: { show: true, distance: 0, color: CHART_INK, formatter: "{b}" },
|
|
98
|
+
labelLayout: { hideOverlap: true },
|
|
99
|
+
data: items,
|
|
100
|
+
markLine: {
|
|
101
|
+
silent: true,
|
|
102
|
+
symbol: "none",
|
|
103
|
+
label: { show: false },
|
|
104
|
+
lineStyle: { color: CHART_INK, width: LEADER_LINE_WIDTH_PX, type: "solid" },
|
|
105
|
+
data: leaders,
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
];
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The series of the text that a figure places itself, for the page and for each export.
|
|
112
|
+
*
|
|
113
|
+
* `build` places the text in one frame and gives the series that carry it. The page takes the series of the page
|
|
114
|
+
* frame, and each export takes the series of the frame of its size, under its width in the member that
|
|
115
|
+
* `exportOption` reads. `names` are the names of the series that `build` can give, thus an export drops each page
|
|
116
|
+
* series of those names, and a size where no text finds a place draws none.
|
|
117
|
+
*/
|
|
118
|
+
export function sizedSeries(names, build) {
|
|
119
|
+
const widths = {};
|
|
120
|
+
for (const [kind, size] of Object.entries(CHART_EXPORT_SIZES)) {
|
|
121
|
+
widths[String(size.widthPx)] = build(EXPORT_PLOT_FRAMES[kind]);
|
|
122
|
+
}
|
|
123
|
+
const sized = { names, widths };
|
|
124
|
+
return { page: build(PAGE_PLOT_FRAME), member: { [SIZED_SERIES_MEMBER]: sized } };
|
|
125
|
+
}
|
|
126
|
+
/** The channel of the quick path that a dense figure demands. An absent channel refuses. */
|
|
127
|
+
export function demandedChannel(blockId, chartType, encoding, channel) {
|
|
128
|
+
const declared = encoding[channel];
|
|
129
|
+
if (declared === undefined) {
|
|
130
|
+
return err(chartProblem(blockId, `The ${chartType} chart needs a column for the "${channel}" channel.`));
|
|
131
|
+
}
|
|
132
|
+
return ok(declared);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* The plotted value of one channel for each row: the transformed value where the channel declares a
|
|
136
|
+
* transform, else the number of the cell. The composition reads the same numbers, thus a rule of the figure
|
|
137
|
+
* and a point of the chart agree.
|
|
138
|
+
*/
|
|
139
|
+
export function plottedValues(rows, channel) {
|
|
140
|
+
const column = channelColumn(channel);
|
|
141
|
+
const transform = channelTransform(channel);
|
|
142
|
+
return transform === undefined ? rows.map((row) => toNumber(row[column])) : transformColumn(rows, column, transform);
|
|
143
|
+
}
|
|
144
|
+
/** The round coefficients of an axis end, in ascending order. */
|
|
145
|
+
const ROUND_COEFFICIENTS = [1, 1.2, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10];
|
|
146
|
+
/**
|
|
147
|
+
* The smallest round number at or above a positive value: one of the round coefficients times a power of ten.
|
|
148
|
+
* A value that is not positive gives one.
|
|
149
|
+
*
|
|
150
|
+
* A figure ends an axis here. The ladder is fine, thus the end leaves little empty room past the data.
|
|
151
|
+
*/
|
|
152
|
+
export function niceCeiling(value) {
|
|
153
|
+
if (!(value > 0))
|
|
154
|
+
return 1;
|
|
155
|
+
const base = Math.pow(10, Math.floor(Math.log10(value)));
|
|
156
|
+
const fraction = Number((value / base).toPrecision(12));
|
|
157
|
+
const coefficient = ROUND_COEFFICIENTS.find((candidate) => candidate >= fraction) ?? 10;
|
|
158
|
+
return Number((coefficient * base).toPrecision(12));
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* The range fields of an axis symmetric around zero, with a tick at each end, at zero, and at each half.
|
|
162
|
+
*
|
|
163
|
+
* The runtime picks its own tick step, and a step that does not divide the end leaves the last tick short of
|
|
164
|
+
* it. The half of the end always divides it, thus the two ends and zero carry a tick.
|
|
165
|
+
*/
|
|
166
|
+
export function symmetricRange(end) {
|
|
167
|
+
return { min: -end, max: end, interval: end / 2 };
|
|
168
|
+
}
|
|
169
|
+
/** The largest value of a list, or `undefined` for an empty list. The loop keeps a long list off the stack. */
|
|
170
|
+
export function largest(values) {
|
|
171
|
+
let top;
|
|
172
|
+
for (const value of values) {
|
|
173
|
+
if (top === undefined || value > top)
|
|
174
|
+
top = value;
|
|
175
|
+
}
|
|
176
|
+
return top;
|
|
177
|
+
}
|
|
178
|
+
/** The smallest value of a list, or `undefined` for an empty list. */
|
|
179
|
+
export function smallest(values) {
|
|
180
|
+
let bottom;
|
|
181
|
+
for (const value of values) {
|
|
182
|
+
if (bottom === undefined || value < bottom)
|
|
183
|
+
bottom = value;
|
|
184
|
+
}
|
|
185
|
+
return bottom;
|
|
186
|
+
}
|
|
187
|
+
/** The series of an option, as objects. */
|
|
188
|
+
export function seriesOf(option) {
|
|
189
|
+
const series = option.series;
|
|
190
|
+
return Array.isArray(series) ? series.filter((entry) => typeof entry === "object" && entry !== null) : [];
|
|
191
|
+
}
|
|
192
|
+
/** The axis of an option as one object. A composition of one grid gives one object for each axis. */
|
|
193
|
+
export function axisOf(option, key) {
|
|
194
|
+
const axis = option[key];
|
|
195
|
+
return typeof axis === "object" && axis !== null && !Array.isArray(axis) ? axis : {};
|
|
196
|
+
}
|
|
197
|
+
/** The fields of one layer of points: the fill, the opacity, the symbol size, and the drawing order. */
|
|
198
|
+
export function pointLayer(color, size, opacity, z) {
|
|
199
|
+
return { itemStyle: { color, opacity }, symbolSize: size, ...(z !== undefined ? { z } : {}) };
|
|
200
|
+
}
|
|
201
|
+
/** The shown text of a count in a legend name: a whole number with a thousands separator. */
|
|
202
|
+
export function countText(count) {
|
|
203
|
+
return formatNumberCell(count, "compact").text;
|
|
204
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dot plot: a marker dot plot of a single-cell study, or the enrichment dot plot of a gene-set test.
|
|
3
|
+
*
|
|
4
|
+
* The canonical marker dot plot is the scanpy `pl.dotplot` and the Seurat `DotPlot`: the clusters down the y
|
|
5
|
+
* axis, the genes along x, the fraction of expressing cells as the dot size, and the mean expression as the
|
|
6
|
+
* dot color. The canonical enrichment dot plot is the enrichplot `dotplot`: the terms down the y axis, the gene
|
|
7
|
+
* ratio along x, the gene count as the dot size, and the adjusted p as the dot color. Each plot shows two
|
|
8
|
+
* keys: a size legend of three reference circles, and a sequential color scale with its two ends.
|
|
9
|
+
*
|
|
10
|
+
* The y axis reads top-down in the order of its channel, thus a table that sorts its terms by their ratio
|
|
11
|
+
* reads from the first term down. The x axis draws categories where its column holds text, and values where
|
|
12
|
+
* it holds numbers.
|
|
13
|
+
*
|
|
14
|
+
* The dots build through the composition machinery. The figure moves each dot onto the place of its category,
|
|
15
|
+
* and the page builds no such move, thus the rows stay inline whatever their count.
|
|
16
|
+
*/
|
|
17
|
+
import type { FigureModule } from "./index.js";
|
|
18
|
+
/** The height of one line of a y label, and the gap between two terms, in pixels at the page text size. */
|
|
19
|
+
export declare const DOTPLOT_LINE_PX = 14;
|
|
20
|
+
export declare const DOTPLOT_ROW_GAP_PX = 6;
|
|
21
|
+
/**
|
|
22
|
+
* One term wrapped at its spaces into lines of 30 characters at most, two lines at most. A term past two lines
|
|
23
|
+
* ends its second line with an ellipsis, thus two terms never run into each other. A word longer than a line
|
|
24
|
+
* stays whole.
|
|
25
|
+
*/
|
|
26
|
+
export declare function wrappedTerm(term: string): string;
|
|
27
|
+
/** The dot plot. */
|
|
28
|
+
export declare const DOTPLOT_FIGURE: FigureModule;
|