@inflexa-ai/harness 0.48.3 → 0.48.5
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/dist/agents/conversation-agent.js +5 -1
- package/dist/agents/sandbox/index.js +3 -0
- package/dist/agents/sandbox/mdv-preparation-agent.d.ts +9 -0
- package/dist/agents/sandbox/mdv-preparation-agent.js +37 -0
- package/dist/agents/sandbox/network-agent.js +1 -1
- package/dist/agents/sandbox/shared.js +3 -1
- package/dist/agents/sandbox/types.d.ts +1 -1
- package/dist/app/chat-turn-workflow.js +1 -0
- package/dist/app/chat-turn.d.ts +4 -2
- package/dist/app/chat-turn.js +46 -46
- package/dist/app/work.d.ts +7 -3
- package/dist/app/work.js +17 -16
- package/dist/contracts/chat-parts.d.ts +46 -5
- package/dist/contracts/chat-parts.js +2 -1
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/part-registry.js +1 -0
- package/dist/contracts/schemas/chat-parts.d.ts +168 -4
- package/dist/contracts/schemas/chat-parts.js +21 -1
- package/dist/execution/run-event-stream.d.ts +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/lib/regulatory-corpus/fetch-ich.js +1 -16
- package/dist/lib/slug.d.ts +18 -0
- package/dist/lib/slug.js +29 -0
- package/dist/lib/test/pdf-fixture.d.ts +5 -0
- package/dist/lib/test/pdf-fixture.js +14 -0
- package/dist/loop/chat-stream.d.ts +4 -1
- package/dist/loop/chat-stream.js +30 -13
- package/dist/loop/run-loop.js +23 -35
- package/dist/loop/sub-agent.d.ts +2 -2
- package/dist/loop/sub-agent.js +1 -1
- package/dist/loop/test/metric-reader.d.ts +9 -0
- package/dist/loop/test/metric-reader.js +33 -0
- package/dist/loop/test/unnamed-model.d.ts +6 -0
- package/dist/loop/test/unnamed-model.js +24 -0
- package/dist/memory/conversation-display-recorder.d.ts +2 -0
- package/dist/memory/conversation-display-recorder.js +6 -2
- package/dist/memory/conversation-display-storage.d.ts +4 -2
- package/dist/memory/conversation-display-storage.js +19 -1
- package/dist/prompts/conversation.d.ts +1 -1
- package/dist/prompts/conversation.js +6 -4
- package/dist/prompts/sandbox/mdv-preparation-agent.d.ts +1 -0
- package/dist/prompts/sandbox/mdv-preparation-agent.js +56 -0
- package/dist/prompts/sandbox/network-agent.d.ts +1 -1
- package/dist/prompts/sandbox/network-agent.js +4 -0
- package/dist/providers/errors.js +2 -1
- package/dist/report-model/fixture-resolver.js +9 -135
- package/dist/report-model/production-resolver.d.ts +3 -3
- package/dist/report-model/production-resolver.js +12 -127
- package/dist/report-model/reference-read.d.ts +38 -0
- package/dist/report-model/reference-read.js +149 -0
- package/dist/report-model/reference-resolver.d.ts +7 -0
- package/dist/report-model/reference-resolver.js +1 -1
- package/dist/report-render/chart.js +48 -46
- package/dist/report-render/figures/common.d.ts +34 -0
- package/dist/report-render/figures/common.js +4 -1
- package/dist/report-render/figures/equal-units.js +2 -4
- package/dist/report-render/figures/sankey.js +1 -3
- package/dist/report-render/figures/upset.js +2 -5
- package/dist/runtime/assemble.js +2 -1
- package/dist/sandbox/execution-id.js +2 -14
- package/dist/sandbox/leases.js +46 -9
- package/dist/schemas/validate-plan.js +2 -14
- package/dist/state/active-turns.d.ts +6 -2
- package/dist/state/active-turns.js +11 -7
- package/dist/state/data-profile.d.ts +8 -0
- package/dist/state/data-profile.js +18 -0
- package/dist/state/index.d.ts +1 -1
- package/dist/state/index.js +1 -1
- package/dist/state/migrations/20261102120000_record_the_waiter_of_a_pause.d.ts +6 -0
- package/dist/state/migrations/20261102120000_record_the_waiter_of_a_pause.js +10 -0
- package/dist/state/migrations/index.js +2 -0
- package/dist/state/paused-work.d.ts +5 -3
- package/dist/state/paused-work.js +11 -7
- package/dist/state/runs.d.ts +3 -2
- package/dist/state/runs.js +4 -3
- package/dist/tasks/data-profile-index.js +3 -17
- package/dist/tasks/data-profile-resolve.js +3 -17
- package/dist/tasks/data-profile.js +1 -0
- package/dist/tools/bio/humanbase.d.ts +32 -0
- package/dist/tools/bio/humanbase.js +180 -0
- package/dist/tools/bio/index.d.ts +3 -1
- package/dist/tools/bio/index.js +3 -1
- package/dist/tools/bio/resolve-gene-ids.d.ts +13 -0
- package/dist/tools/bio/resolve-gene-ids.js +67 -0
- package/dist/tools/lib/humanbase-client.d.ts +215 -0
- package/dist/tools/lib/humanbase-client.js +187 -0
- package/dist/tools/lib/mygene-client.d.ts +150 -0
- package/dist/tools/lib/mygene-client.js +109 -0
- package/dist/tools/lib/opentargets-client.js +15 -25
- package/dist/tools/report-authoring/authoring-tools.d.ts +3 -3
- package/dist/tools/report-authoring/authoring-tools.js +7 -6
- package/dist/tools/research/generate-analogy-report.js +10 -2
- package/dist/tools/script/run-script.js +48 -51
- package/dist/workflows/execute-analysis.js +4 -3
- package/dist/workflows/pause.d.ts +3 -3
- package/dist/workflows/pause.js +15 -9
- package/dist/workflows/sandbox-step.js +1 -0
- package/package.json +7 -4
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
* holds the streamed hash and the parsed rows against a stat signature. Two tool calls of one analysis
|
|
14
14
|
* share one cache.
|
|
15
15
|
*
|
|
16
|
-
* The realization calls the shared assert functions in `assert-rules.ts
|
|
17
|
-
*
|
|
18
|
-
* executable specification of the value tier. The two realizations are substitutable behind the seam.
|
|
16
|
+
* The realization calls the shared assert functions in `assert-rules.ts` and the shared reference reads in
|
|
17
|
+
* `reference-read.ts`, as the fixture realization does. Thus one semantics exists, and the fixture stays
|
|
18
|
+
* the executable specification of the value tier. The two realizations are substitutable behind the seam.
|
|
19
19
|
*/
|
|
20
20
|
import { err, ok } from "neverthrow";
|
|
21
21
|
import { readFile, stat } from "node:fs/promises";
|
|
@@ -26,8 +26,9 @@ import { allWithConcurrency } from "../lib/async-utils.js";
|
|
|
26
26
|
import { computeSha256File } from "../lib/fs-helpers.js";
|
|
27
27
|
import { parseJsonAs } from "../lib/parse-json.js";
|
|
28
28
|
import { resolveWorkspacePath } from "../workspace/paths.js";
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
29
|
+
import { checkValueAssertion } from "./assert-rules.js";
|
|
30
|
+
import { fail, readArtifactTable, resolveCitation, resolveDerivation, selectRow } from "./reference-read.js";
|
|
31
|
+
import { fileTypeHoldsNoCell, snapshotEntry } from "./reference-resolver.js";
|
|
31
32
|
/**
|
|
32
33
|
* The default cap on the file size that the host fast path reads. A larger file takes the fall-through.
|
|
33
34
|
* The cap gates the cell extraction only. The identity compare streams the hash at every size.
|
|
@@ -95,15 +96,6 @@ export function createArtifactReadStore() {
|
|
|
95
96
|
function fallThroughCells(path, hostDetail) {
|
|
96
97
|
return { kind: "unavailable", detail: `the host read of the file at ${path} stopped because ${hostDetail}, and no other reader exists` };
|
|
97
98
|
}
|
|
98
|
-
/** Build an `Err` that carries the unresolved reference. The `detail` key is present only when there is a detail to carry. */
|
|
99
|
-
function fail(reference, reason, detail) {
|
|
100
|
-
const failure = detail !== undefined ? { reference, reason, detail } : { reference, reason };
|
|
101
|
-
return err(failure);
|
|
102
|
-
}
|
|
103
|
-
/** A short account of a derivation input, for a detail that names which input broke the arithmetic. */
|
|
104
|
-
function describeReference(reference) {
|
|
105
|
-
return `artifact value at ${reference.path} column ${reference.locator.column}`;
|
|
106
|
-
}
|
|
107
99
|
/**
|
|
108
100
|
* Map a file extension to the format that the host arm reads. An unknown extension gives back `undefined`.
|
|
109
101
|
*
|
|
@@ -538,36 +530,15 @@ export function createProductionResolver(deps) {
|
|
|
538
530
|
/** Walk the locator over the rows, and match the resolved cell against the authored belief. */
|
|
539
531
|
function selectCell(reference, rows) {
|
|
540
532
|
const locator = reference.locator;
|
|
541
|
-
|
|
542
|
-
if (
|
|
543
|
-
|
|
544
|
-
if (row === undefined) {
|
|
545
|
-
return fail(reference, "locator-out-of-range", `row ${locator.row} is outside the ${rows.length} rows`);
|
|
546
|
-
}
|
|
547
|
-
selectedRow = row;
|
|
548
|
-
}
|
|
549
|
-
else if (locator.rowFilter !== undefined) {
|
|
550
|
-
const filter = locator.rowFilter;
|
|
551
|
-
const matches = rows.filter((row) => cellMatchesFilterValue(row[filter.column], filter.value));
|
|
552
|
-
const match = matches[0];
|
|
553
|
-
if (match === undefined) {
|
|
554
|
-
return fail(reference, "locator-out-of-range", `no row where ${filter.column} equals ${String(filter.value)}`);
|
|
555
|
-
}
|
|
556
|
-
if (matches.length > 1) {
|
|
557
|
-
return fail(reference, "ambiguous-match", `${matches.length} rows where ${filter.column} equals ${String(filter.value)}`);
|
|
558
|
-
}
|
|
559
|
-
selectedRow = match;
|
|
560
|
-
}
|
|
561
|
-
else {
|
|
562
|
-
// The locator schema forbids a locator with neither selector. The resolver cannot depend on a
|
|
563
|
-
// prior parse, thus it treats a row that no selector addresses as out of range.
|
|
564
|
-
return fail(reference, "locator-out-of-range", "the locator selects no row");
|
|
533
|
+
const selection = selectRow(rows, locator);
|
|
534
|
+
if (selection.isErr()) {
|
|
535
|
+
return fail(reference, selection.error.reason, selection.error.detail);
|
|
565
536
|
}
|
|
566
537
|
// A real table holds an empty cell, and a parse gives it back as an absent key. An absent key is
|
|
567
538
|
// not a value that a scalar reference can bind. An empty string is a value, thus it stays valid.
|
|
568
539
|
// A cell that parsed as `null` never lands in a row: `coerceCell` drops it, so `null` is not
|
|
569
540
|
// an absence the resolver can meet here.
|
|
570
|
-
const cell =
|
|
541
|
+
const cell = selection.value[locator.column];
|
|
571
542
|
if (cell === undefined) {
|
|
572
543
|
return fail(reference, "locator-out-of-range", `column ${locator.column} holds no value in the selected row`);
|
|
573
544
|
}
|
|
@@ -583,41 +554,7 @@ export function createProductionResolver(deps) {
|
|
|
583
554
|
if (gated.isErr()) {
|
|
584
555
|
return err(gated.error);
|
|
585
556
|
}
|
|
586
|
-
|
|
587
|
-
// below leaves out still ranks them. An unknown bound column addresses nothing, exactly as an
|
|
588
|
-
// unknown subset column does.
|
|
589
|
-
const bound = reference.rowBound;
|
|
590
|
-
const allRows = gated.value;
|
|
591
|
-
if (bound !== undefined && columnsHeldByNoRow(allRows, [bound.column]).length > 0) {
|
|
592
|
-
return fail(reference, "locator-out-of-range", `the row bound names column ${bound.column}, which the table at ${reference.path} does not hold`);
|
|
593
|
-
}
|
|
594
|
-
const rows = bound === undefined ? allRows : applyRowBound(allRows, bound);
|
|
595
|
-
// The page states the shown count against the count of the artifact. This read is the one place that
|
|
596
|
-
// holds both, thus a bounded table carries the pre-bound total out with its rows.
|
|
597
|
-
const total = bound === undefined ? {} : { total: allRows.length };
|
|
598
|
-
const columns = reference.columns;
|
|
599
|
-
if (columns === undefined) {
|
|
600
|
-
return ok({ type: "table", rows, ...total });
|
|
601
|
-
}
|
|
602
|
-
// A name that no row holds addresses nothing. Without this check a projection onto an invented
|
|
603
|
-
// column gives an empty cell for each row and still resolves, which grounding exists to reject.
|
|
604
|
-
const absent = columnsHeldByNoRow(rows, columns);
|
|
605
|
-
if (absent.length > 0) {
|
|
606
|
-
return fail(reference, "locator-out-of-range", `the table at ${reference.path} holds no column ${absent.join(", ")}`);
|
|
607
|
-
}
|
|
608
|
-
// Project each row onto the requested columns. A table tolerates a ragged row, thus a column that
|
|
609
|
-
// a given row lacks is left out of that row and is not a failure.
|
|
610
|
-
const projectedRows = rows.map((row) => {
|
|
611
|
-
const projected = {};
|
|
612
|
-
for (const column of columns) {
|
|
613
|
-
const cell = row[column];
|
|
614
|
-
if (cell !== undefined) {
|
|
615
|
-
projected[column] = cell;
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
return projected;
|
|
619
|
-
});
|
|
620
|
-
return ok({ type: "table", rows: projectedRows, columns, ...total });
|
|
557
|
+
return readArtifactTable(reference, gated.value);
|
|
621
558
|
}
|
|
622
559
|
async function resolveArtifactFile(reference, snapshot) {
|
|
623
560
|
const identity = await getIdentity(reference.path, snapshot);
|
|
@@ -632,58 +569,6 @@ export function createProductionResolver(deps) {
|
|
|
632
569
|
}
|
|
633
570
|
return ok({ type: "file", path: reference.path, hash: identity.onDiskHash });
|
|
634
571
|
}
|
|
635
|
-
async function resolveDerivation(reference, snapshot) {
|
|
636
|
-
const numbers = [];
|
|
637
|
-
for (const input of reference.inputs) {
|
|
638
|
-
const inputResult = await resolveArtifactValue(input, snapshot);
|
|
639
|
-
if (inputResult.isErr()) {
|
|
640
|
-
// Keep the inner reason so a reviewer sees the real cause, for example a missing artifact
|
|
641
|
-
// under the derivation. The derivation itself did not fail on its own terms.
|
|
642
|
-
return fail(reference, inputResult.error.reason, inputResult.error.detail);
|
|
643
|
-
}
|
|
644
|
-
const resolved = inputResult.value;
|
|
645
|
-
const numeric = resolved.type === "scalar" ? asFiniteNumber(resolved.value) : undefined;
|
|
646
|
-
if (numeric === undefined) {
|
|
647
|
-
// The arithmetic needs a finite number. A cell that holds text does not address a usable
|
|
648
|
-
// scalar, thus the closest reason is that the coordinate is out of range.
|
|
649
|
-
return fail(reference, "locator-out-of-range", `input ${describeReference(input)} did not resolve to a finite number`);
|
|
650
|
-
}
|
|
651
|
-
numbers.push(numeric);
|
|
652
|
-
}
|
|
653
|
-
const a = numbers[0];
|
|
654
|
-
const b = numbers[1];
|
|
655
|
-
if (a === undefined || b === undefined) {
|
|
656
|
-
return fail(reference, "locator-out-of-range", `operation ${reference.op} needs two inputs, and the derivation holds ${numbers.length}`);
|
|
657
|
-
}
|
|
658
|
-
let result;
|
|
659
|
-
switch (reference.op) {
|
|
660
|
-
case "ratio":
|
|
661
|
-
result = a / b;
|
|
662
|
-
break;
|
|
663
|
-
case "delta":
|
|
664
|
-
result = a - b;
|
|
665
|
-
break;
|
|
666
|
-
case "pctChange":
|
|
667
|
-
// A fraction, not a percent. A change of one half gives 0.5, thus an author asserts 0.5 and
|
|
668
|
-
// a `unit` of `%` only tells a renderer how to show it.
|
|
669
|
-
result = (a - b) / b;
|
|
670
|
-
break;
|
|
671
|
-
}
|
|
672
|
-
if (!Number.isFinite(result)) {
|
|
673
|
-
return fail(reference, "locator-out-of-range", `operation ${reference.op} does not yield a finite value, because the divisor is zero`);
|
|
674
|
-
}
|
|
675
|
-
const valueFailure = checkValueAssertion(reference, reference.assert?.value, reference.assert?.tolerance, result);
|
|
676
|
-
return valueFailure !== undefined ? err(valueFailure) : ok({ type: "scalar", value: result });
|
|
677
|
-
}
|
|
678
|
-
function resolveCitation(reference, snapshot) {
|
|
679
|
-
const key = `${reference.idKind}:${reference.id}`;
|
|
680
|
-
if (snapshot.citations === undefined || !snapshot.citations.includes(key)) {
|
|
681
|
-
return fail(reference, "artifact-missing", `the citation ${key} is not in the pinned evidence`);
|
|
682
|
-
}
|
|
683
|
-
const value = { type: "citation", id: key };
|
|
684
|
-
const citationFailure = checkCitationAssertion(reference, reference.assert?.value, key);
|
|
685
|
-
return citationFailure !== undefined ? err(citationFailure) : ok(value);
|
|
686
|
-
}
|
|
687
572
|
async function resolve(reference, snapshot) {
|
|
688
573
|
switch (reference.kind) {
|
|
689
574
|
case "artifact-value":
|
|
@@ -693,7 +578,7 @@ export function createProductionResolver(deps) {
|
|
|
693
578
|
case "artifact-file":
|
|
694
579
|
return resolveArtifactFile(reference, snapshot);
|
|
695
580
|
case "derivation":
|
|
696
|
-
return resolveDerivation(reference, snapshot);
|
|
581
|
+
return resolveDerivation(reference, snapshot, resolveArtifactValue);
|
|
697
582
|
case "citation":
|
|
698
583
|
return resolveCitation(reference, snapshot);
|
|
699
584
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The reads of a report reference that the two resolver realizations share.
|
|
3
|
+
*
|
|
4
|
+
* The fixture resolver reads the rows of a snapshot, and the production resolver reads the rows of the
|
|
5
|
+
* pinned artifact on disk. What both do with those rows — the locator walk, the table read, and the
|
|
6
|
+
* derivation arithmetic — lives here, so one semantics exists for both realizations.
|
|
7
|
+
*/
|
|
8
|
+
import { type Result } from "neverthrow";
|
|
9
|
+
import type { ArtifactTableReference, ArtifactValueReference, CitationReference, DerivationInputReference, DerivationReference, Reference, UnresolvedReason, UnresolvedReference } from "../contracts/report-reference.js";
|
|
10
|
+
import { type ReportSnapshot, type ResolvedValue } from "./reference-resolver.js";
|
|
11
|
+
/** Build an `Err` that carries the unresolved reference. The `detail` key is present only when there is a detail to carry. */
|
|
12
|
+
export declare function fail(reference: Reference, reason: UnresolvedReason, detail?: string): Result<ResolvedValue, UnresolvedReference>;
|
|
13
|
+
/** The failure of a row selection: the reason that an `UnresolvedReference` carries, and its detail. */
|
|
14
|
+
export type RowSelectionFailure = {
|
|
15
|
+
reason: "locator-out-of-range" | "ambiguous-match";
|
|
16
|
+
detail: string;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Walk a locator over the rows, and give back the one row that it selects, or the failure.
|
|
20
|
+
*
|
|
21
|
+
* A row index selects the row at that position. A row filter selects the one row that matches; no match
|
|
22
|
+
* selects nothing, and two or more matches are ambiguous.
|
|
23
|
+
*/
|
|
24
|
+
export declare function selectRow(rows: Array<Record<string, string | number>>, locator: ArtifactValueReference["locator"]): Result<Record<string, string | number>, RowSelectionFailure>;
|
|
25
|
+
/**
|
|
26
|
+
* Read the table that a table reference addresses out of rows that the caller read from the artifact:
|
|
27
|
+
* cut the rows on the row bound, refuse a column that no row holds, and project the requested columns.
|
|
28
|
+
*/
|
|
29
|
+
export declare function readArtifactTable(reference: ArtifactTableReference, allRows: Array<Record<string, string | number>>): Result<ResolvedValue, UnresolvedReference>;
|
|
30
|
+
/**
|
|
31
|
+
* Resolve a derivation: read each input with `resolveInput`, then run the arithmetic of the operation.
|
|
32
|
+
*
|
|
33
|
+
* The read of an input stays with the realization: the fixture resolver reads its snapshot, and the
|
|
34
|
+
* production resolver reads the pinned artifact on disk.
|
|
35
|
+
*/
|
|
36
|
+
export declare function resolveDerivation(reference: DerivationReference, snapshot: ReportSnapshot, resolveInput: (input: DerivationInputReference, snapshot: ReportSnapshot) => Promise<Result<ResolvedValue, UnresolvedReference>>): Promise<Result<ResolvedValue, UnresolvedReference>>;
|
|
37
|
+
/** Resolve a citation: confirm that the id is in the pinned evidence, and echo the key back. */
|
|
38
|
+
export declare function resolveCitation(reference: CitationReference, snapshot: ReportSnapshot): Result<ResolvedValue, UnresolvedReference>;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The reads of a report reference that the two resolver realizations share.
|
|
3
|
+
*
|
|
4
|
+
* The fixture resolver reads the rows of a snapshot, and the production resolver reads the rows of the
|
|
5
|
+
* pinned artifact on disk. What both do with those rows — the locator walk, the table read, and the
|
|
6
|
+
* derivation arithmetic — lives here, so one semantics exists for both realizations.
|
|
7
|
+
*/
|
|
8
|
+
import { err, ok } from "neverthrow";
|
|
9
|
+
import { asFiniteNumber, cellMatchesFilterValue, checkCitationAssertion, checkValueAssertion } from "./assert-rules.js";
|
|
10
|
+
import { applyRowBound, columnsHeldByNoRow, defineOwn } from "./reference-resolver.js";
|
|
11
|
+
/** Build an `Err` that carries the unresolved reference. The `detail` key is present only when there is a detail to carry. */
|
|
12
|
+
export function fail(reference, reason, detail) {
|
|
13
|
+
const failure = detail !== undefined ? { reference, reason, detail } : { reference, reason };
|
|
14
|
+
return err(failure);
|
|
15
|
+
}
|
|
16
|
+
/** A short account of a derivation input, for a detail that names which input broke the arithmetic. */
|
|
17
|
+
function describeReference(reference) {
|
|
18
|
+
return `artifact value at ${reference.path} column ${reference.locator.column}`;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Walk a locator over the rows, and give back the one row that it selects, or the failure.
|
|
22
|
+
*
|
|
23
|
+
* A row index selects the row at that position. A row filter selects the one row that matches; no match
|
|
24
|
+
* selects nothing, and two or more matches are ambiguous.
|
|
25
|
+
*/
|
|
26
|
+
export function selectRow(rows, locator) {
|
|
27
|
+
if (locator.row !== undefined) {
|
|
28
|
+
const row = rows[locator.row];
|
|
29
|
+
if (row === undefined) {
|
|
30
|
+
return err({ reason: "locator-out-of-range", detail: `row ${locator.row} is outside the ${rows.length} rows` });
|
|
31
|
+
}
|
|
32
|
+
return ok(row);
|
|
33
|
+
}
|
|
34
|
+
if (locator.rowFilter !== undefined) {
|
|
35
|
+
const filter = locator.rowFilter;
|
|
36
|
+
const matches = rows.filter((row) => cellMatchesFilterValue(row[filter.column], filter.value));
|
|
37
|
+
const match = matches[0];
|
|
38
|
+
if (match === undefined) {
|
|
39
|
+
return err({ reason: "locator-out-of-range", detail: `no row where ${filter.column} equals ${String(filter.value)}` });
|
|
40
|
+
}
|
|
41
|
+
if (matches.length > 1) {
|
|
42
|
+
return err({ reason: "ambiguous-match", detail: `${matches.length} rows where ${filter.column} equals ${String(filter.value)}` });
|
|
43
|
+
}
|
|
44
|
+
return ok(match);
|
|
45
|
+
}
|
|
46
|
+
// The locator schema forbids a locator with neither selector. The resolver cannot depend on a
|
|
47
|
+
// prior parse, thus it treats a row that no selector addresses as out of range.
|
|
48
|
+
return err({ reason: "locator-out-of-range", detail: "the locator selects no row" });
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Read the table that a table reference addresses out of rows that the caller read from the artifact:
|
|
52
|
+
* cut the rows on the row bound, refuse a column that no row holds, and project the requested columns.
|
|
53
|
+
*/
|
|
54
|
+
export function readArtifactTable(reference, allRows) {
|
|
55
|
+
// The bound ranks the rows as the artifact holds them, thus a bound over a column that the subset
|
|
56
|
+
// below leaves out still ranks them. An unknown bound column addresses nothing, exactly as an
|
|
57
|
+
// unknown subset column does.
|
|
58
|
+
const bound = reference.rowBound;
|
|
59
|
+
if (bound !== undefined && columnsHeldByNoRow(allRows, [bound.column]).length > 0) {
|
|
60
|
+
return fail(reference, "locator-out-of-range", `the row bound names column ${bound.column}, which the table at ${reference.path} does not hold`);
|
|
61
|
+
}
|
|
62
|
+
const rows = bound === undefined ? allRows : applyRowBound(allRows, bound);
|
|
63
|
+
// The page states the shown count against the count of the artifact. This read is the one place that
|
|
64
|
+
// holds both, thus a bounded table carries the pre-bound total out with its rows.
|
|
65
|
+
const total = bound === undefined ? {} : { total: allRows.length };
|
|
66
|
+
const columns = reference.columns;
|
|
67
|
+
if (columns === undefined) {
|
|
68
|
+
return ok({ type: "table", rows, ...total });
|
|
69
|
+
}
|
|
70
|
+
// A name that no row holds addresses nothing. Without this check a projection onto an invented column
|
|
71
|
+
// gives an empty cell for each row and still resolves, which is the fabrication that grounding exists
|
|
72
|
+
// to reject.
|
|
73
|
+
const absent = columnsHeldByNoRow(rows, columns);
|
|
74
|
+
if (absent.length > 0) {
|
|
75
|
+
return fail(reference, "locator-out-of-range", `the table at ${reference.path} holds no column ${absent.join(", ")}`);
|
|
76
|
+
}
|
|
77
|
+
// Project each row onto the requested columns. A table tolerates a ragged row, thus a column that a
|
|
78
|
+
// given row lacks is left out of that row and is not a failure.
|
|
79
|
+
const projectedRows = rows.map((row) => {
|
|
80
|
+
const projected = {};
|
|
81
|
+
for (const column of columns) {
|
|
82
|
+
const cell = row[column];
|
|
83
|
+
if (cell !== undefined) {
|
|
84
|
+
defineOwn(projected, column, cell);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return projected;
|
|
88
|
+
});
|
|
89
|
+
return ok({ type: "table", rows: projectedRows, columns, ...total });
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Resolve a derivation: read each input with `resolveInput`, then run the arithmetic of the operation.
|
|
93
|
+
*
|
|
94
|
+
* The read of an input stays with the realization: the fixture resolver reads its snapshot, and the
|
|
95
|
+
* production resolver reads the pinned artifact on disk.
|
|
96
|
+
*/
|
|
97
|
+
export async function resolveDerivation(reference, snapshot, resolveInput) {
|
|
98
|
+
const numbers = [];
|
|
99
|
+
for (const input of reference.inputs) {
|
|
100
|
+
const inputResult = await resolveInput(input, snapshot);
|
|
101
|
+
if (inputResult.isErr()) {
|
|
102
|
+
// Keep the inner reason so a reviewer sees the real cause, for example a missing artifact under
|
|
103
|
+
// the derivation. The derivation itself did not fail on its own terms.
|
|
104
|
+
return fail(reference, inputResult.error.reason, inputResult.error.detail);
|
|
105
|
+
}
|
|
106
|
+
const resolved = inputResult.value;
|
|
107
|
+
const numeric = resolved.type === "scalar" ? asFiniteNumber(resolved.value) : undefined;
|
|
108
|
+
if (numeric === undefined) {
|
|
109
|
+
// The arithmetic needs a finite number. A cell that holds text does not address a usable
|
|
110
|
+
// scalar, thus the closest reason is that the coordinate is out of range.
|
|
111
|
+
return fail(reference, "locator-out-of-range", `input ${describeReference(input)} did not resolve to a finite number`);
|
|
112
|
+
}
|
|
113
|
+
numbers.push(numeric);
|
|
114
|
+
}
|
|
115
|
+
const a = numbers[0];
|
|
116
|
+
const b = numbers[1];
|
|
117
|
+
if (a === undefined || b === undefined) {
|
|
118
|
+
return fail(reference, "locator-out-of-range", `operation ${reference.op} needs two inputs, and the derivation holds ${numbers.length}`);
|
|
119
|
+
}
|
|
120
|
+
let result;
|
|
121
|
+
switch (reference.op) {
|
|
122
|
+
case "ratio":
|
|
123
|
+
result = a / b;
|
|
124
|
+
break;
|
|
125
|
+
case "delta":
|
|
126
|
+
result = a - b;
|
|
127
|
+
break;
|
|
128
|
+
case "pctChange":
|
|
129
|
+
// A fraction, not a percent. A change of one half gives 0.5, thus an author asserts 0.5 and a
|
|
130
|
+
// `unit` of `%` only tells a renderer how to show it.
|
|
131
|
+
result = (a - b) / b;
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
if (!Number.isFinite(result)) {
|
|
135
|
+
return fail(reference, "locator-out-of-range", `operation ${reference.op} does not yield a finite value, because the divisor is zero`);
|
|
136
|
+
}
|
|
137
|
+
const valueFailure = checkValueAssertion(reference, reference.assert?.value, reference.assert?.tolerance, result);
|
|
138
|
+
return valueFailure !== undefined ? err(valueFailure) : ok({ type: "scalar", value: result });
|
|
139
|
+
}
|
|
140
|
+
/** Resolve a citation: confirm that the id is in the pinned evidence, and echo the key back. */
|
|
141
|
+
export function resolveCitation(reference, snapshot) {
|
|
142
|
+
const key = `${reference.idKind}:${reference.id}`;
|
|
143
|
+
if (snapshot.citations === undefined || !snapshot.citations.includes(key)) {
|
|
144
|
+
return fail(reference, "artifact-missing", `the citation ${key} is not in the pinned evidence`);
|
|
145
|
+
}
|
|
146
|
+
const value = { type: "citation", id: key };
|
|
147
|
+
const citationFailure = checkCitationAssertion(reference, reference.assert?.value, key);
|
|
148
|
+
return citationFailure !== undefined ? err(citationFailure) : ok(value);
|
|
149
|
+
}
|
|
@@ -34,6 +34,13 @@ export type ArtifactSnapshot = {
|
|
|
34
34
|
fileType?: string | null;
|
|
35
35
|
rows?: Array<Record<string, string | number | null | undefined>>;
|
|
36
36
|
};
|
|
37
|
+
/**
|
|
38
|
+
* Put one own data property on a row.
|
|
39
|
+
*
|
|
40
|
+
* A plain assignment at the key `__proto__` reaches the prototype setter, thus the row loses a column
|
|
41
|
+
* that the payload carries. The definition makes an own data property for each key.
|
|
42
|
+
*/
|
|
43
|
+
export declare function defineOwn(target: Record<string, string | number>, key: string, value: string | number): void;
|
|
37
44
|
/**
|
|
38
45
|
* Give back the present cells of a snapshot entry's rows, or an empty array for an entry with no rows.
|
|
39
46
|
*
|
|
@@ -16,7 +16,7 @@ const CellSchema = z.union([z.string(), z.number()]).nullish();
|
|
|
16
16
|
* A plain assignment at the key `__proto__` reaches the prototype setter, thus the row loses a column
|
|
17
17
|
* that the payload carries. The definition makes an own data property for each key.
|
|
18
18
|
*/
|
|
19
|
-
function defineOwn(target, key, value) {
|
|
19
|
+
export function defineOwn(target, key, value) {
|
|
20
20
|
Object.defineProperty(target, key, { value, enumerable: true, writable: true, configurable: true });
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
@@ -888,18 +888,10 @@ function deriveTransformedRows(rows, derived) {
|
|
|
888
888
|
* parts of a category.
|
|
889
889
|
*/
|
|
890
890
|
function deriveBar(block, rows, columns, mode) {
|
|
891
|
-
const
|
|
892
|
-
if (
|
|
893
|
-
return err(
|
|
894
|
-
const
|
|
895
|
-
if (yResult.isErr())
|
|
896
|
-
return err(yResult.error);
|
|
897
|
-
const x = xResult.value;
|
|
898
|
-
const y = yResult.value;
|
|
899
|
-
const ordered = orderQuickCategories(block.id, rows, x, block.orders.x);
|
|
900
|
-
if (ordered.isErr())
|
|
901
|
-
return err(ordered.error);
|
|
902
|
-
const categories = ordered.value;
|
|
891
|
+
const axes = axesOf(block, rows, columns);
|
|
892
|
+
if (axes.isErr())
|
|
893
|
+
return err(axes.error);
|
|
894
|
+
const { x, y, categories } = axes.value;
|
|
903
895
|
const horizontal = block.orientation === "horizontal";
|
|
904
896
|
let series;
|
|
905
897
|
if (mode === "bar") {
|
|
@@ -1188,23 +1180,14 @@ function deriveHistogram(block, rows, columns) {
|
|
|
1188
1180
|
* point inside the slot of its group.
|
|
1189
1181
|
*/
|
|
1190
1182
|
function deriveBox(block, rows, columns) {
|
|
1191
|
-
const
|
|
1192
|
-
if (
|
|
1193
|
-
return err(
|
|
1194
|
-
const
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
const
|
|
1199
|
-
const ordered = orderQuickCategories(block.id, rows, x, block.orders.x);
|
|
1200
|
-
if (ordered.isErr())
|
|
1201
|
-
return err(ordered.error);
|
|
1202
|
-
const categories = ordered.value;
|
|
1203
|
-
const groupColumn = block.encoding.group;
|
|
1204
|
-
const groups = groupColumn === undefined ? [{ name: undefined }] : firstAppearance(rows.map((row) => row[groupColumn])).map((name) => ({ name }));
|
|
1205
|
-
const bound = slotFault(block.id, categories.length, groups.length);
|
|
1206
|
-
if (bound !== undefined)
|
|
1207
|
-
return err(bound);
|
|
1183
|
+
const axes = axesOf(block, rows, columns);
|
|
1184
|
+
if (axes.isErr())
|
|
1185
|
+
return err(axes.error);
|
|
1186
|
+
const { x, y, categories } = axes.value;
|
|
1187
|
+
const slots = slotGroupsOf(block, rows, categories);
|
|
1188
|
+
if (slots.isErr())
|
|
1189
|
+
return err(slots.error);
|
|
1190
|
+
const { groupColumn, groups } = slots.value;
|
|
1208
1191
|
const focused = focusSet(block.id, block.focus, rows.length, groups.flatMap((group) => (group.name === undefined ? [] : [group.name])));
|
|
1209
1192
|
if (focused.isErr())
|
|
1210
1193
|
return err(focused.error);
|
|
@@ -1446,23 +1429,14 @@ function derivePie(block, rows, columns) {
|
|
|
1446
1429
|
* violin draws its points on the hidden slot axis, each point inside the slot of its group.
|
|
1447
1430
|
*/
|
|
1448
1431
|
function deriveViolin(block, rows, columns) {
|
|
1449
|
-
const
|
|
1450
|
-
if (
|
|
1451
|
-
return err(
|
|
1452
|
-
const
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
const
|
|
1457
|
-
const ordered = orderQuickCategories(block.id, rows, x, block.orders.x);
|
|
1458
|
-
if (ordered.isErr())
|
|
1459
|
-
return err(ordered.error);
|
|
1460
|
-
const categories = ordered.value;
|
|
1461
|
-
const groupColumn = block.encoding.group;
|
|
1462
|
-
const groups = groupColumn === undefined ? [{ name: undefined }] : firstAppearance(rows.map((row) => row[groupColumn])).map((name) => ({ name }));
|
|
1463
|
-
const bound = slotFault(block.id, categories.length, groups.length);
|
|
1464
|
-
if (bound !== undefined)
|
|
1465
|
-
return err(bound);
|
|
1432
|
+
const axes = axesOf(block, rows, columns);
|
|
1433
|
+
if (axes.isErr())
|
|
1434
|
+
return err(axes.error);
|
|
1435
|
+
const { x, y, categories } = axes.value;
|
|
1436
|
+
const slots = slotGroupsOf(block, rows, categories);
|
|
1437
|
+
if (slots.isErr())
|
|
1438
|
+
return err(slots.error);
|
|
1439
|
+
const { groupColumn, groups } = slots.value;
|
|
1466
1440
|
const named = groups.flatMap((group) => (group.name === undefined ? [] : [group.name]));
|
|
1467
1441
|
const focused = focusSet(block.id, block.focus, rows.length, named);
|
|
1468
1442
|
if (focused.isErr())
|
|
@@ -1651,6 +1625,34 @@ function slotFault(blockId, categories, groups) {
|
|
|
1651
1625
|
return undefined;
|
|
1652
1626
|
return problem(blockId, `The chart holds ${categories} categories and ${groups} groups, thus ${slots} slots. A chart holds ${CHART_SLOT_LIMIT} slots at most, thus a table of fewer categories or fewer groups serves the reader.`);
|
|
1653
1627
|
}
|
|
1628
|
+
/**
|
|
1629
|
+
* The x and y columns and the category order that the category derivations share: each one demands both
|
|
1630
|
+
* channels and reads its categories through the quick-path order.
|
|
1631
|
+
*/
|
|
1632
|
+
function axesOf(block, rows, columns) {
|
|
1633
|
+
const xResult = requireColumn(block, rows, columns, "x");
|
|
1634
|
+
if (xResult.isErr())
|
|
1635
|
+
return err(xResult.error);
|
|
1636
|
+
const yResult = requireColumn(block, rows, columns, "y");
|
|
1637
|
+
if (yResult.isErr())
|
|
1638
|
+
return err(yResult.error);
|
|
1639
|
+
const ordered = orderQuickCategories(block.id, rows, xResult.value, block.orders.x);
|
|
1640
|
+
if (ordered.isErr())
|
|
1641
|
+
return err(ordered.error);
|
|
1642
|
+
return ok({ x: xResult.value, y: yResult.value, categories: ordered.value });
|
|
1643
|
+
}
|
|
1644
|
+
/**
|
|
1645
|
+
* The slot layout that the box and the violin share: a chart with no group channel holds one unnamed group,
|
|
1646
|
+
* and a layout past the slot limit refuses before any series is built.
|
|
1647
|
+
*/
|
|
1648
|
+
function slotGroupsOf(block, rows, categories) {
|
|
1649
|
+
const groupColumn = block.encoding.group;
|
|
1650
|
+
const groups = groupColumn === undefined ? [{ name: undefined }] : firstAppearance(rows.map((row) => row[groupColumn])).map((name) => ({ name }));
|
|
1651
|
+
const bound = slotFault(block.id, categories.length, groups.length);
|
|
1652
|
+
if (bound !== undefined)
|
|
1653
|
+
return err(bound);
|
|
1654
|
+
return ok({ groupColumn, groups });
|
|
1655
|
+
}
|
|
1654
1656
|
/** The radius of a radar. The legend and the indicator names sit outside it. */
|
|
1655
1657
|
const RADAR_RADIUS = "62%";
|
|
1656
1658
|
/** The tier of one row count. The two counts are constants of the design source. */
|
|
@@ -22,6 +22,40 @@ import { type ChannelOrder, type ChartEncoding, type ChartTransform } from "../.
|
|
|
22
22
|
import { type ArtifactTableReference, type ColumnMeaning } from "../../contracts/report-reference.js";
|
|
23
23
|
import type { Cell, ChartRow, EchartOption } from "../chart.js";
|
|
24
24
|
import type { RenderProblem } from "../types.js";
|
|
25
|
+
/** The size of one chart box and its text: the page, or one export. */
|
|
26
|
+
export type ChartBox = {
|
|
27
|
+
readonly widthPx: number;
|
|
28
|
+
readonly heightPx: number;
|
|
29
|
+
readonly textPx: number;
|
|
30
|
+
};
|
|
31
|
+
/** The page box, and the export sizes that take a rule of their own: the two journal columns and the slide. */
|
|
32
|
+
export declare const PAGE_BOX: ChartBox;
|
|
33
|
+
export declare const EXPORT_SIZES: readonly [{
|
|
34
|
+
readonly theme: "inflexa-print";
|
|
35
|
+
readonly textPx: 9.33;
|
|
36
|
+
readonly widthPx: 336;
|
|
37
|
+
readonly heightPx: 253;
|
|
38
|
+
readonly widthMm: 89;
|
|
39
|
+
readonly heightMm: 67;
|
|
40
|
+
readonly pixelRatio: number;
|
|
41
|
+
readonly label: "89 mm";
|
|
42
|
+
}, {
|
|
43
|
+
readonly theme: "inflexa-print";
|
|
44
|
+
readonly textPx: 9.33;
|
|
45
|
+
readonly widthPx: 692;
|
|
46
|
+
readonly heightPx: 348;
|
|
47
|
+
readonly widthMm: 183;
|
|
48
|
+
readonly heightMm: 92;
|
|
49
|
+
readonly pixelRatio: number;
|
|
50
|
+
readonly label: "183 mm";
|
|
51
|
+
}, {
|
|
52
|
+
readonly theme: "inflexa-slide";
|
|
53
|
+
readonly textPx: 24;
|
|
54
|
+
readonly widthPx: 1920;
|
|
55
|
+
readonly heightPx: 1080;
|
|
56
|
+
readonly pixelRatio: 1;
|
|
57
|
+
readonly label: "16:9";
|
|
58
|
+
}];
|
|
25
59
|
/** The display labels that a bound table declares, keyed by the raw column name. */
|
|
26
60
|
export type ColumnLabels = ArtifactTableReference["columnLabels"];
|
|
27
61
|
/** The column meanings that a bound table declares, keyed by the raw column name. */
|
|
@@ -23,9 +23,12 @@ import { channelColumn, channelOrder, channelTransform } from "../../contracts/r
|
|
|
23
23
|
import { declaredForColumn } from "../../contracts/report-reference.js";
|
|
24
24
|
import { isUnknownArray } from "../../lib/unknown-array.js";
|
|
25
25
|
import { parseUnknownRecord } from "../../lib/unknown-containers.js";
|
|
26
|
-
import { CHART_BODY_MAX_PX, CHART_FONT_STACK, CHART_INK, CHART_PAGE_TEXT_PX, CHART_PALETTE, CHART_WIDE_PALETTE, DIVERGING_RAMP, GUIDE_LINE_COLOR, GUIDE_LINE_WIDTH_PX, MUTED_CHART_COLOR, SEQUENTIAL_RAMP, SIZE_CHANNEL_RANGE_PX, } from "../design.js";
|
|
26
|
+
import { CHART_BODY_MAX_PX, CHART_BODY_PX, CHART_EXPORT_SIZES, CHART_FONT_STACK, CHART_INK, CHART_PAGE_TEXT_PX, CHART_PAGE_WIDTH_PX, CHART_PALETTE, CHART_WIDE_PALETTE, DIVERGING_RAMP, GUIDE_LINE_COLOR, GUIDE_LINE_WIDTH_PX, MUTED_CHART_COLOR, SEQUENTIAL_RAMP, SIZE_CHANNEL_RANGE_PX, } from "../design.js";
|
|
27
27
|
import { formatNumberCell, selectNumberKind, typographicExponent } from "../number-format.js";
|
|
28
28
|
import { STEM_RENDERER } from "../chart-renderers.js";
|
|
29
|
+
/** The page box, and the export sizes that take a rule of their own: the two journal columns and the slide. */
|
|
30
|
+
export const PAGE_BOX = { widthPx: CHART_PAGE_WIDTH_PX, heightPx: CHART_BODY_PX, textPx: CHART_PAGE_TEXT_PX };
|
|
31
|
+
export const EXPORT_SIZES = [CHART_EXPORT_SIZES.single, CHART_EXPORT_SIZES.double, CHART_EXPORT_SIZES.slide];
|
|
29
32
|
// ── The cells ───────────────────────────────────────────────────────────────
|
|
30
33
|
/** A typed `invalid-chart-input` problem that names the block and the cause. */
|
|
31
34
|
export function chartProblem(blockId, detail) {
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
* largest container, thus no other container meets it.
|
|
23
23
|
*/
|
|
24
24
|
import { TOOLBOX_BAND_PX } from "../chart-toolbox.js";
|
|
25
|
-
import {
|
|
26
|
-
import { legendLineCount, legendLinePx } from "./common.js";
|
|
25
|
+
import { CHART_PAGE_TEXT_PX, FACET_COLUMNS } from "../design.js";
|
|
26
|
+
import { EXPORT_SIZES, legendLineCount, legendLinePx } from "./common.js";
|
|
27
27
|
/** The smallest square side, in pixels. A container smaller than its rule still draws this square. */
|
|
28
28
|
const SQUARE_MIN_PX = 120;
|
|
29
29
|
/** The largest square side, in pixels. The slide export is the largest container. */
|
|
@@ -83,8 +83,6 @@ export function squareLayout(fields) {
|
|
|
83
83
|
media,
|
|
84
84
|
};
|
|
85
85
|
}
|
|
86
|
-
/** The export sizes that take a rule of their own: the two journal columns and the slide. */
|
|
87
|
-
const EXPORT_SIZES = [CHART_EXPORT_SIZES.single, CHART_EXPORT_SIZES.double, CHART_EXPORT_SIZES.slide];
|
|
88
86
|
/** The scale of the margins of a page square: one up to the page square, and larger with a larger square. */
|
|
89
87
|
function marginScale(side) {
|
|
90
88
|
return Math.min(MARGIN_SCALE_MAX, Math.max(1, side / MARGIN_SCALE_FROM_PX));
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
import { err, ok } from "neverthrow";
|
|
26
26
|
import { declaredForColumn } from "../../contracts/report-reference.js";
|
|
27
27
|
import { CHART_BODY_PX, CHART_EXPORT_SIZES, CHART_INK, CHART_PAGE_TEXT_PX, CHART_PAGE_WIDTH_PX, MUTED_CHART_COLOR } from "../design.js";
|
|
28
|
-
import { AREA_LEGEND_ICON, categoricalPalette, chartProblem, demandedColumn, firstAppearance, legendLineCount, legendLinePx, plainColumn, toNumber, } from "./common.js";
|
|
28
|
+
import { AREA_LEGEND_ICON, categoricalPalette, chartProblem, demandedColumn, EXPORT_SIZES, firstAppearance, legendLineCount, legendLinePx, PAGE_BOX, plainColumn, toNumber, } from "./common.js";
|
|
29
29
|
import { largest } from "./dense.js";
|
|
30
30
|
/** The opacity of a flow. A flow reads as the path of its source, and a label reads over it. */
|
|
31
31
|
export const SANKEY_LINK_OPACITY = 0.35;
|
|
@@ -182,8 +182,6 @@ function readFlows(rows, source, target, value, group) {
|
|
|
182
182
|
}
|
|
183
183
|
return ok(flows);
|
|
184
184
|
}
|
|
185
|
-
const PAGE_BOX = { widthPx: CHART_PAGE_WIDTH_PX, heightPx: CHART_BODY_PX, textPx: CHART_PAGE_TEXT_PX };
|
|
186
|
-
const EXPORT_SIZES = [CHART_EXPORT_SIZES.single, CHART_EXPORT_SIZES.double, CHART_EXPORT_SIZES.slide];
|
|
187
185
|
/**
|
|
188
186
|
* The text and the node size of one box: the label text size, the room of a label between two stages, the node
|
|
189
187
|
* width, and the node gap. A figure of one stage gives its label the half of the width.
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
* each export size states the dot size and the turn of the count text there.
|
|
26
26
|
*/
|
|
27
27
|
import { err, ok } from "neverthrow";
|
|
28
|
-
import {
|
|
29
|
-
import { axisNameFields, chartProblem, demandedColumn, valueAxis } from "./common.js";
|
|
28
|
+
import { CHART_EXPORT_SIZES, CHART_INK } from "../design.js";
|
|
29
|
+
import { axisNameFields, chartProblem, demandedColumn, EXPORT_SIZES, PAGE_BOX, valueAxis } from "./common.js";
|
|
30
30
|
/**
|
|
31
31
|
* The most sets that one figure draws. Each set takes one row of the matrix, and the rows of twelve sets hold
|
|
32
32
|
* one line of the column-export text each.
|
|
@@ -125,9 +125,6 @@ function compareMembers(a, b) {
|
|
|
125
125
|
}
|
|
126
126
|
return a.length - b.length;
|
|
127
127
|
}
|
|
128
|
-
/** The page box and the box of each export size. */
|
|
129
|
-
const PAGE_BOX = { widthPx: CHART_PAGE_WIDTH_PX, heightPx: CHART_BODY_PX, textPx: CHART_PAGE_TEXT_PX };
|
|
130
|
-
const EXPORT_SIZES = [CHART_EXPORT_SIZES.single, CHART_EXPORT_SIZES.double, CHART_EXPORT_SIZES.slide];
|
|
131
128
|
/**
|
|
132
129
|
* The places of the grids for some sets, and set names of some length.
|
|
133
130
|
*
|