@platforma-sdk/model 1.63.12 → 1.65.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/dist/columns/column_collection_builder.cjs +105 -92
- package/dist/columns/column_collection_builder.cjs.map +1 -1
- package/dist/columns/column_collection_builder.d.ts +13 -12
- package/dist/columns/column_collection_builder.d.ts.map +1 -1
- package/dist/columns/column_collection_builder.js +107 -94
- package/dist/columns/column_collection_builder.js.map +1 -1
- package/dist/columns/column_selector.cjs +8 -80
- package/dist/columns/column_selector.cjs.map +1 -1
- package/dist/columns/column_selector.d.ts +6 -14
- package/dist/columns/column_selector.d.ts.map +1 -1
- package/dist/columns/column_selector.js +6 -77
- package/dist/columns/column_selector.js.map +1 -1
- package/dist/columns/column_snapshot.cjs +3 -3
- package/dist/columns/column_snapshot.cjs.map +1 -1
- package/dist/columns/column_snapshot.d.ts +3 -3
- package/dist/columns/column_snapshot.d.ts.map +1 -1
- package/dist/columns/column_snapshot.js +3 -3
- package/dist/columns/column_snapshot.js.map +1 -1
- package/dist/columns/column_snapshot_provider.cjs +1 -1
- package/dist/columns/column_snapshot_provider.cjs.map +1 -1
- package/dist/columns/column_snapshot_provider.d.ts +8 -8
- package/dist/columns/column_snapshot_provider.d.ts.map +1 -1
- package/dist/columns/column_snapshot_provider.js +1 -1
- package/dist/columns/column_snapshot_provider.js.map +1 -1
- package/dist/columns/ctx_column_sources.cjs.map +1 -1
- package/dist/columns/ctx_column_sources.d.ts +2 -1
- package/dist/columns/ctx_column_sources.d.ts.map +1 -1
- package/dist/columns/ctx_column_sources.js.map +1 -1
- package/dist/columns/expand_by_partition.cjs +106 -0
- package/dist/columns/expand_by_partition.cjs.map +1 -0
- package/dist/columns/expand_by_partition.d.ts +33 -0
- package/dist/columns/expand_by_partition.d.ts.map +1 -0
- package/dist/columns/expand_by_partition.js +105 -0
- package/dist/columns/expand_by_partition.js.map +1 -0
- package/dist/columns/index.cjs +1 -0
- package/dist/columns/index.d.ts +4 -3
- package/dist/columns/index.js +1 -0
- package/dist/components/PlDataTable/createPlDataTable/createPTableDefV2.cjs +26 -0
- package/dist/components/PlDataTable/createPlDataTable/createPTableDefV2.cjs.map +1 -0
- package/dist/components/PlDataTable/createPlDataTable/createPTableDefV2.js +25 -0
- package/dist/components/PlDataTable/createPlDataTable/createPTableDefV2.js.map +1 -0
- package/dist/components/PlDataTable/createPlDataTable/createPTableDefV3.cjs +68 -0
- package/dist/components/PlDataTable/createPlDataTable/createPTableDefV3.cjs.map +1 -0
- package/dist/components/PlDataTable/createPlDataTable/createPTableDefV3.js +67 -0
- package/dist/components/PlDataTable/createPlDataTable/createPTableDefV3.js.map +1 -0
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.cjs +27 -17
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.cjs.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.d.ts +4 -0
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.d.ts.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.js +28 -18
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.js.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.cjs +258 -175
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.cjs.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.d.ts +37 -21
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.d.ts.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.js +261 -175
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.js.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/discoverColumns.cjs +64 -0
- package/dist/components/PlDataTable/createPlDataTable/discoverColumns.cjs.map +1 -0
- package/dist/components/PlDataTable/createPlDataTable/discoverColumns.d.ts +17 -0
- package/dist/components/PlDataTable/createPlDataTable/discoverColumns.d.ts.map +1 -0
- package/dist/components/PlDataTable/createPlDataTable/discoverColumns.js +63 -0
- package/dist/components/PlDataTable/createPlDataTable/discoverColumns.js.map +1 -0
- package/dist/components/PlDataTable/createPlDataTable/index.cjs +2 -1
- package/dist/components/PlDataTable/createPlDataTable/index.cjs.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/index.d.ts +2 -1
- package/dist/components/PlDataTable/createPlDataTable/index.d.ts.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/index.js +2 -1
- package/dist/components/PlDataTable/createPlDataTable/index.js.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/utils.cjs +109 -0
- package/dist/components/PlDataTable/createPlDataTable/utils.cjs.map +1 -0
- package/dist/components/PlDataTable/createPlDataTable/utils.d.ts +19 -0
- package/dist/components/PlDataTable/createPlDataTable/utils.d.ts.map +1 -0
- package/dist/components/PlDataTable/createPlDataTable/utils.js +102 -0
- package/dist/components/PlDataTable/createPlDataTable/utils.js.map +1 -0
- package/dist/components/PlDataTable/index.cjs +3 -1
- package/dist/components/PlDataTable/index.d.ts +5 -3
- package/dist/components/PlDataTable/index.js +3 -1
- package/dist/components/PlDataTable/labels.cjs +25 -11
- package/dist/components/PlDataTable/labels.cjs.map +1 -1
- package/dist/components/PlDataTable/labels.js +25 -11
- package/dist/components/PlDataTable/labels.js.map +1 -1
- package/dist/components/PlDataTable/state-migration.cjs +8 -2
- package/dist/components/PlDataTable/state-migration.cjs.map +1 -1
- package/dist/components/PlDataTable/state-migration.d.ts.map +1 -1
- package/dist/components/PlDataTable/state-migration.js +8 -2
- package/dist/components/PlDataTable/state-migration.js.map +1 -1
- package/dist/components/PlDataTable/typesV5.d.ts +23 -15
- package/dist/components/PlDataTable/typesV5.d.ts.map +1 -1
- package/dist/components/index.cjs +3 -1
- package/dist/components/index.d.ts +4 -2
- package/dist/components/index.js +3 -1
- package/dist/index.cjs +13 -9
- package/dist/index.d.ts +9 -7
- package/dist/index.js +6 -4
- package/dist/labels/derive_distinct_labels.cjs +39 -27
- package/dist/labels/derive_distinct_labels.cjs.map +1 -1
- package/dist/labels/derive_distinct_labels.d.ts +15 -15
- package/dist/labels/derive_distinct_labels.d.ts.map +1 -1
- package/dist/labels/derive_distinct_labels.js +39 -27
- package/dist/labels/derive_distinct_labels.js.map +1 -1
- package/dist/labels/index.cjs +0 -1
- package/dist/labels/index.d.ts +1 -2
- package/dist/labels/index.js +0 -1
- package/dist/package.cjs +1 -1
- package/dist/package.js +1 -1
- package/dist/render/api.cjs +10 -3
- package/dist/render/api.cjs.map +1 -1
- package/dist/render/api.d.ts +2 -2
- package/dist/render/api.d.ts.map +1 -1
- package/dist/render/api.js +10 -3
- package/dist/render/api.js.map +1 -1
- package/dist/render/util/column_collection.cjs +3 -3
- package/dist/render/util/column_collection.cjs.map +1 -1
- package/dist/render/util/column_collection.d.ts.map +1 -1
- package/dist/render/util/column_collection.js +3 -3
- package/dist/render/util/column_collection.js.map +1 -1
- package/dist/render/util/label.cjs +2 -2
- package/dist/render/util/label.cjs.map +1 -1
- package/dist/render/util/label.js +2 -2
- package/dist/render/util/label.js.map +1 -1
- package/dist/render/util/pcolumn_data.cjs.map +1 -1
- package/dist/render/util/pcolumn_data.d.ts +2 -2
- package/dist/render/util/pcolumn_data.d.ts.map +1 -1
- package/dist/render/util/pcolumn_data.js.map +1 -1
- package/package.json +7 -7
- package/src/columns/column_collection_builder.test.ts +40 -27
- package/src/columns/column_collection_builder.ts +176 -131
- package/src/columns/column_selector.test.ts +17 -399
- package/src/columns/column_selector.ts +14 -127
- package/src/columns/column_snapshot.ts +5 -5
- package/src/columns/column_snapshot_provider.ts +11 -10
- package/src/columns/ctx_column_sources.ts +2 -2
- package/src/columns/expand_by_partition.test.ts +4 -4
- package/src/columns/expand_by_partition.ts +4 -3
- package/src/columns/index.ts +1 -0
- package/src/components/PlDataTable/createPlDataTable/createPTableDefV2.ts +42 -0
- package/src/components/PlDataTable/createPlDataTable/createPTableDefV3.ts +89 -0
- package/src/components/PlDataTable/createPlDataTable/createPlDataTableV2.ts +51 -19
- package/src/components/PlDataTable/createPlDataTable/createPlDataTableV3.ts +500 -313
- package/src/components/PlDataTable/createPlDataTable/discoverColumns.ts +122 -0
- package/src/components/PlDataTable/createPlDataTable/index.ts +4 -2
- package/src/components/PlDataTable/createPlDataTable/utils.test.ts +257 -0
- package/src/components/PlDataTable/createPlDataTable/utils.ts +160 -0
- package/src/components/PlDataTable/index.ts +15 -2
- package/src/components/PlDataTable/labels.ts +29 -18
- package/src/components/PlDataTable/state-migration.ts +6 -1
- package/src/components/PlDataTable/typesV5.ts +25 -12
- package/src/labels/derive_distinct_labels.test.ts +143 -45
- package/src/labels/derive_distinct_labels.ts +102 -49
- package/src/labels/index.ts +0 -1
- package/src/render/api.ts +15 -5
- package/src/render/util/column_collection.ts +4 -3
- package/src/render/util/label.ts +2 -2
- package/src/render/util/pcolumn_data.ts +5 -3
- package/dist/labels/write_labels_to_specs.cjs +0 -14
- package/dist/labels/write_labels_to_specs.cjs.map +0 -1
- package/dist/labels/write_labels_to_specs.d.ts +0 -7
- package/dist/labels/write_labels_to_specs.d.ts.map +0 -1
- package/dist/labels/write_labels_to_specs.js +0 -13
- package/dist/labels/write_labels_to_specs.js.map +0 -1
- package/src/labels/write_labels_to_specs.ts +0 -12
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
PColumnSpec,
|
|
3
|
+
PlRef,
|
|
4
|
+
PObjectId,
|
|
5
|
+
RequireServices,
|
|
6
|
+
Services,
|
|
7
|
+
SUniversalPColumnId,
|
|
8
|
+
} from "@milaboratories/pl-model-common";
|
|
9
|
+
import { isPlRef } from "@milaboratories/pl-model-common";
|
|
10
|
+
import type { RenderCtxBase } from "../../../render";
|
|
11
|
+
import type { ColumnSource, ColumnMatch, RelaxedColumnSelector } from "../../../columns";
|
|
12
|
+
import { ColumnCollectionBuilder } from "../../../columns";
|
|
13
|
+
import { toColumnSnapshotProvider } from "../../../columns/column_snapshot_provider";
|
|
14
|
+
import { collectCtxColumnSnapshotProviders } from "../../../columns/ctx_column_sources";
|
|
15
|
+
import { throwError } from "@milaboratories/helpers";
|
|
16
|
+
import type { ColumnsSelectorConfig, TableColumnSnapshot } from "./createPlDataTableV3";
|
|
17
|
+
|
|
18
|
+
export type DiscoveredTableColumnOptions = {
|
|
19
|
+
sources?: ColumnSource[];
|
|
20
|
+
anchors: Record<string, PlRef | PObjectId | PColumnSpec | RelaxedColumnSelector>;
|
|
21
|
+
columnsSelector: ColumnsSelectorConfig;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** Discover columns from sources/anchors and normalize into a flat DiscoveredColumn list. */
|
|
25
|
+
export function discoverTableColumnSnaphots<
|
|
26
|
+
A,
|
|
27
|
+
U,
|
|
28
|
+
S extends RequireServices<typeof Services.PFrameSpec>,
|
|
29
|
+
>(
|
|
30
|
+
ctx: RenderCtxBase<A, U, S>,
|
|
31
|
+
options: DiscoveredTableColumnOptions,
|
|
32
|
+
): TableColumnSnapshot<SUniversalPColumnId>[] | undefined {
|
|
33
|
+
// Resolve PlRef anchors to PColumnSpec
|
|
34
|
+
const resolvedOptions = { ...options, anchors: resolveAnchors(ctx, options.anchors) };
|
|
35
|
+
|
|
36
|
+
// Resolve providers
|
|
37
|
+
const providers = resolveProviders(ctx, resolvedOptions.sources);
|
|
38
|
+
if (providers.length === 0) return undefined;
|
|
39
|
+
|
|
40
|
+
// Build collection (anchored or plain)
|
|
41
|
+
const pframeSpec =
|
|
42
|
+
ctx.services.pframeSpec ?? throwError("PFrameSpec service is required for column discovery.");
|
|
43
|
+
const collection = new ColumnCollectionBuilder(pframeSpec)
|
|
44
|
+
.addSources(providers)
|
|
45
|
+
.build(resolvedOptions);
|
|
46
|
+
if (collection === undefined) return undefined;
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const matched = collection.findColumns(resolvedOptions.columnsSelector ?? undefined);
|
|
50
|
+
const anchors = collection.getAnchors();
|
|
51
|
+
return mapToDiscoveredColumns(
|
|
52
|
+
matched,
|
|
53
|
+
Array.from(Array.from(anchors.values()).map((v) => v.columnId)),
|
|
54
|
+
);
|
|
55
|
+
} finally {
|
|
56
|
+
collection.dispose();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// --- Pure helper functions ---
|
|
61
|
+
|
|
62
|
+
/** Resolve PlRef values in anchors to PColumnSpec via the result pool. */
|
|
63
|
+
function resolveAnchors<A, U>(
|
|
64
|
+
ctx: RenderCtxBase<A, U>,
|
|
65
|
+
anchors: Record<string, PlRef | PObjectId | PColumnSpec | RelaxedColumnSelector>,
|
|
66
|
+
): Record<string, PObjectId | PColumnSpec | RelaxedColumnSelector> {
|
|
67
|
+
const result: Record<string, PObjectId | PColumnSpec | RelaxedColumnSelector> = {};
|
|
68
|
+
for (const [key, value] of Object.entries(anchors)) {
|
|
69
|
+
if (isPlRef(value)) {
|
|
70
|
+
result[key] =
|
|
71
|
+
ctx.resultPool.getPColumnSpecByRef(value) ??
|
|
72
|
+
throwError(
|
|
73
|
+
`Anchor ${key} with ref ${JSON.stringify(value)} could not be resolved to a PColumnSpec`,
|
|
74
|
+
);
|
|
75
|
+
} else {
|
|
76
|
+
result[key] = value;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Resolve column snapshot providers from explicit sources or context. */
|
|
83
|
+
function resolveProviders<A, U>(ctx: RenderCtxBase<A, U>, sources: undefined | ColumnSource[]) {
|
|
84
|
+
return sources !== undefined
|
|
85
|
+
? sources.map(toColumnSnapshotProvider)
|
|
86
|
+
: collectCtxColumnSnapshotProviders(ctx);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Map matched columns into a flat DiscoveredColumn list with deduped IDs. */
|
|
90
|
+
function mapToDiscoveredColumns(
|
|
91
|
+
matched: readonly ColumnMatch[],
|
|
92
|
+
anchorColumnIds: readonly PObjectId[],
|
|
93
|
+
): TableColumnSnapshot<SUniversalPColumnId>[] {
|
|
94
|
+
const hitCounts = matched.reduce(
|
|
95
|
+
(acc, match) => acc.set(match.originalId, (acc.get(match.originalId) ?? 0) + 1),
|
|
96
|
+
new Map<PObjectId, number>(),
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const hitIndices = new Map<PObjectId, number>();
|
|
100
|
+
return matched.map((match) => {
|
|
101
|
+
const snap = match.column;
|
|
102
|
+
|
|
103
|
+
let id = snap.id;
|
|
104
|
+
if ((hitCounts.get(match.originalId) ?? 0) > 1) {
|
|
105
|
+
const idx = hitIndices.get(match.originalId) ?? 0;
|
|
106
|
+
hitIndices.set(match.originalId, idx + 1);
|
|
107
|
+
id = `${snap.id}#q${idx}` as SUniversalPColumnId;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
id,
|
|
112
|
+
originalId: match.originalId,
|
|
113
|
+
spec: snap.spec,
|
|
114
|
+
data: snap.data,
|
|
115
|
+
dataStatus: snap.dataStatus,
|
|
116
|
+
isPrimary: anchorColumnIds.some(
|
|
117
|
+
(anchor) => snap.id === anchor || match.originalId === anchor,
|
|
118
|
+
),
|
|
119
|
+
linkerPath: match.path,
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
}
|
|
@@ -5,8 +5,8 @@ import { createPlDataTableOptionsV2, createPlDataTableV2 } from "./createPlDataT
|
|
|
5
5
|
import { createPlDataTableV3 } from "./createPlDataTableV3";
|
|
6
6
|
import type { createPlDataTableOptionsV3 } from "./createPlDataTableV3";
|
|
7
7
|
|
|
8
|
-
export function createPlDataTable<A, U
|
|
9
|
-
ctx: RenderCtxBase<A, U>,
|
|
8
|
+
export function createPlDataTable<A, U, S extends RequireServices<typeof Services.PFrameSpec>>(
|
|
9
|
+
ctx: RenderCtxBase<A, U, S>,
|
|
10
10
|
options: { version: "v2" } & createPlDataTableOptionsV2,
|
|
11
11
|
): ReturnType<typeof createPlDataTableV2>;
|
|
12
12
|
export function createPlDataTable<A, U, S extends RequireServices<typeof Services.PFrameSpec>>(
|
|
@@ -26,3 +26,5 @@ export function createPlDataTable<A, U, S extends RequireServices<typeof Service
|
|
|
26
26
|
return createPlDataTableV3(ctx, options);
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
+
|
|
30
|
+
export { discoverTableColumnSnaphots } from "./discoverColumns";
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Annotation,
|
|
3
|
+
canonicalizeJson,
|
|
4
|
+
getAxisId,
|
|
5
|
+
type AxisId,
|
|
6
|
+
type PColumnSpec,
|
|
7
|
+
type PObjectId,
|
|
8
|
+
} from "@milaboratories/pl-model-common";
|
|
9
|
+
import { describe, expect, test } from "vitest";
|
|
10
|
+
import { deriveAllLabels, type LabelableColumn } from "./utils";
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Helpers
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
function makeSpec(overrides: Partial<PColumnSpec> = {}): PColumnSpec {
|
|
17
|
+
return {
|
|
18
|
+
kind: "PColumn",
|
|
19
|
+
name: "col",
|
|
20
|
+
valueType: "Int",
|
|
21
|
+
annotations: {},
|
|
22
|
+
axesSpec: [],
|
|
23
|
+
...overrides,
|
|
24
|
+
} as PColumnSpec;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function makeLabelableColumn(
|
|
28
|
+
id: string,
|
|
29
|
+
spec: Partial<PColumnSpec> = {},
|
|
30
|
+
linkerPath?: LabelableColumn["linkerPath"],
|
|
31
|
+
): LabelableColumn {
|
|
32
|
+
return {
|
|
33
|
+
id: id as PObjectId,
|
|
34
|
+
spec: makeSpec(spec),
|
|
35
|
+
linkerPath,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function makeLabelColumn(
|
|
40
|
+
axisSpec: PColumnSpec["axesSpec"][0],
|
|
41
|
+
label: string,
|
|
42
|
+
): { readonly spec: PColumnSpec } {
|
|
43
|
+
return {
|
|
44
|
+
spec: makeSpec({
|
|
45
|
+
name: "label-col",
|
|
46
|
+
valueType: "String",
|
|
47
|
+
annotations: { [Annotation.Label]: label },
|
|
48
|
+
axesSpec: [axisSpec],
|
|
49
|
+
}),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function axisKey(axis: PColumnSpec["axesSpec"][0]): string {
|
|
54
|
+
return canonicalizeJson<AxisId>(getAxisId(axis));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// deriveAllLabels
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
describe("deriveAllLabels", () => {
|
|
62
|
+
test("returns column labels derived from deriveDistinctLabels", () => {
|
|
63
|
+
const columns: LabelableColumn[] = [
|
|
64
|
+
makeLabelableColumn("c1", {
|
|
65
|
+
name: "alpha",
|
|
66
|
+
annotations: { [Annotation.Label]: "Alpha" },
|
|
67
|
+
}),
|
|
68
|
+
makeLabelableColumn("c2", {
|
|
69
|
+
name: "beta",
|
|
70
|
+
annotations: { [Annotation.Label]: "Beta" },
|
|
71
|
+
}),
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
const result = deriveAllLabels({
|
|
75
|
+
columns,
|
|
76
|
+
labelColumns: [],
|
|
77
|
+
deriveLabelsOptions: { includeNativeLabel: true },
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
expect(result["c1"]).toBe("Alpha");
|
|
81
|
+
expect(result["c2"]).toBe("Beta");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("returns axis labels from label columns when available", () => {
|
|
85
|
+
const axis = { type: "String", name: "sample" } as const;
|
|
86
|
+
const columns: LabelableColumn[] = [
|
|
87
|
+
makeLabelableColumn("c1", {
|
|
88
|
+
name: "score",
|
|
89
|
+
axesSpec: [axis],
|
|
90
|
+
annotations: { [Annotation.Label]: "Score" },
|
|
91
|
+
}),
|
|
92
|
+
];
|
|
93
|
+
const labelColumns = [makeLabelColumn(axis, "Sample Name")];
|
|
94
|
+
|
|
95
|
+
const result = deriveAllLabels({
|
|
96
|
+
columns,
|
|
97
|
+
labelColumns,
|
|
98
|
+
deriveLabelsOptions: { includeNativeLabel: true },
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
expect(result[axisKey(axis)]).toBe("Sample Name");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("falls back to axis annotation when no label column matches", () => {
|
|
105
|
+
const axis = { type: "String", name: "gene" } as const;
|
|
106
|
+
const columns: LabelableColumn[] = [
|
|
107
|
+
makeLabelableColumn("c1", {
|
|
108
|
+
name: "expr",
|
|
109
|
+
axesSpec: [{ ...axis, annotations: { [Annotation.Label]: "Gene ID" } }],
|
|
110
|
+
annotations: { [Annotation.Label]: "Expression" },
|
|
111
|
+
}),
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
const result = deriveAllLabels({
|
|
115
|
+
columns,
|
|
116
|
+
labelColumns: [],
|
|
117
|
+
deriveLabelsOptions: { includeNativeLabel: true },
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
expect(result[axisKey(axis)]).toBe("Gene ID");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("axis with no label annotation and no label column gets 'Unlabeled'", () => {
|
|
124
|
+
const axis = { type: "Int", name: "idx" } as const;
|
|
125
|
+
const columns: LabelableColumn[] = [
|
|
126
|
+
makeLabelableColumn("c1", {
|
|
127
|
+
name: "val",
|
|
128
|
+
axesSpec: [axis],
|
|
129
|
+
annotations: { [Annotation.Label]: "Value" },
|
|
130
|
+
}),
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
const result = deriveAllLabels({
|
|
134
|
+
columns,
|
|
135
|
+
labelColumns: [],
|
|
136
|
+
deriveLabelsOptions: { includeNativeLabel: true },
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
expect(result[axisKey(axis)]).toBe("Unlabeled");
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("deduplicates axes shared across columns", () => {
|
|
143
|
+
const sharedAxis = { type: "String", name: "sample" } as const;
|
|
144
|
+
const columns: LabelableColumn[] = [
|
|
145
|
+
makeLabelableColumn("c1", {
|
|
146
|
+
name: "score1",
|
|
147
|
+
axesSpec: [sharedAxis],
|
|
148
|
+
annotations: { [Annotation.Label]: "Score 1" },
|
|
149
|
+
}),
|
|
150
|
+
makeLabelableColumn("c2", {
|
|
151
|
+
name: "score2",
|
|
152
|
+
axesSpec: [sharedAxis],
|
|
153
|
+
annotations: { [Annotation.Label]: "Score 2" },
|
|
154
|
+
}),
|
|
155
|
+
];
|
|
156
|
+
const labelColumns = [makeLabelColumn(sharedAxis, "Sample")];
|
|
157
|
+
|
|
158
|
+
const result = deriveAllLabels({
|
|
159
|
+
columns,
|
|
160
|
+
labelColumns,
|
|
161
|
+
deriveLabelsOptions: { includeNativeLabel: true },
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// axis label appears once
|
|
165
|
+
expect(result[axisKey(sharedAxis)]).toBe("Sample");
|
|
166
|
+
// column labels are distinct
|
|
167
|
+
expect(result["c1"]).toBe("Score 1");
|
|
168
|
+
expect(result["c2"]).toBe("Score 2");
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
// deriveAxisLabels (tested through deriveAllLabels)
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
|
|
176
|
+
describe("deriveAxisLabels via deriveAllLabels", () => {
|
|
177
|
+
test("multiple axes each get their own label", () => {
|
|
178
|
+
const axis1 = { type: "String", name: "sample" } as const;
|
|
179
|
+
const axis2 = { type: "String", name: "chain" } as const;
|
|
180
|
+
const columns: LabelableColumn[] = [
|
|
181
|
+
makeLabelableColumn("c1", {
|
|
182
|
+
name: "value",
|
|
183
|
+
axesSpec: [axis1, axis2],
|
|
184
|
+
annotations: { [Annotation.Label]: "Value" },
|
|
185
|
+
}),
|
|
186
|
+
];
|
|
187
|
+
const labelColumns = [
|
|
188
|
+
makeLabelColumn(axis1, "Sample ID"),
|
|
189
|
+
makeLabelColumn(axis2, "Chain Type"),
|
|
190
|
+
];
|
|
191
|
+
|
|
192
|
+
const result = deriveAllLabels({
|
|
193
|
+
columns,
|
|
194
|
+
labelColumns,
|
|
195
|
+
deriveLabelsOptions: { includeNativeLabel: true },
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
expect(result[axisKey(axis1)]).toBe("Sample ID");
|
|
199
|
+
expect(result[axisKey(axis2)]).toBe("Chain Type");
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("label column overrides axis annotation", () => {
|
|
203
|
+
const axis = {
|
|
204
|
+
type: "String",
|
|
205
|
+
name: "sample",
|
|
206
|
+
annotations: { [Annotation.Label]: "Axis Annotation Label" },
|
|
207
|
+
} as const;
|
|
208
|
+
const columns: LabelableColumn[] = [
|
|
209
|
+
makeLabelableColumn("c1", {
|
|
210
|
+
name: "v",
|
|
211
|
+
axesSpec: [axis],
|
|
212
|
+
annotations: { [Annotation.Label]: "V" },
|
|
213
|
+
}),
|
|
214
|
+
];
|
|
215
|
+
const labelColumns = [makeLabelColumn({ type: "String", name: "sample" }, "Label Col Label")];
|
|
216
|
+
|
|
217
|
+
const result = deriveAllLabels({
|
|
218
|
+
columns,
|
|
219
|
+
labelColumns,
|
|
220
|
+
deriveLabelsOptions: { includeNativeLabel: true },
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
expect(result[axisKey(axis)]).toBe("Label Col Label");
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Regression: LabelableColumn.linkerPath used to be `linkerPath` while
|
|
227
|
+
// deriveDistinctLabels.Entry expected `linkersPath`. Structural typing +
|
|
228
|
+
// optional fields hid the mismatch from the type checker — the field flowed
|
|
229
|
+
// through as "extra" and disambiguation silently broke. This asserts the
|
|
230
|
+
// linker label reaches the output.
|
|
231
|
+
test("linkerPath flows into deriveDistinctLabels and produces the 'via' suffix", () => {
|
|
232
|
+
const linkerSpec = makeSpec({
|
|
233
|
+
name: "linker",
|
|
234
|
+
annotations: { [Annotation.LinkLabel]: "ClusterA" },
|
|
235
|
+
});
|
|
236
|
+
const columns: LabelableColumn[] = [
|
|
237
|
+
makeLabelableColumn(
|
|
238
|
+
"c1",
|
|
239
|
+
{ name: "shared", annotations: { [Annotation.Label]: "Cluster size" } },
|
|
240
|
+
[{ spec: linkerSpec }],
|
|
241
|
+
),
|
|
242
|
+
makeLabelableColumn("c2", {
|
|
243
|
+
name: "shared",
|
|
244
|
+
annotations: { [Annotation.Label]: "Cluster size" },
|
|
245
|
+
}),
|
|
246
|
+
];
|
|
247
|
+
|
|
248
|
+
const result = deriveAllLabels({
|
|
249
|
+
columns,
|
|
250
|
+
labelColumns: [],
|
|
251
|
+
deriveLabelsOptions: { includeNativeLabel: true },
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
expect(result["c1"]).toContain("via ClusterA");
|
|
255
|
+
expect(result["c2"]).not.toContain("via ");
|
|
256
|
+
});
|
|
257
|
+
});
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type PColumn,
|
|
3
|
+
type PColumnSpec,
|
|
4
|
+
type PObjectId,
|
|
5
|
+
Annotation,
|
|
6
|
+
canonicalizeAxisId,
|
|
7
|
+
canonicalizeJson,
|
|
8
|
+
getAxisId,
|
|
9
|
+
readAnnotation,
|
|
10
|
+
readAnnotationJson,
|
|
11
|
+
} from "@milaboratories/pl-model-common";
|
|
12
|
+
import {
|
|
13
|
+
deriveDistinctLabels,
|
|
14
|
+
type DeriveLabelsOptions,
|
|
15
|
+
} from "../../../labels/derive_distinct_labels";
|
|
16
|
+
import type { ColumnsDisplayOptions } from "./createPlDataTableV3";
|
|
17
|
+
import { isNil } from "es-toolkit";
|
|
18
|
+
|
|
19
|
+
/** Check if column should be omitted from the table */
|
|
20
|
+
export function isColumnHidden(spec: { annotations?: Annotation }): boolean {
|
|
21
|
+
return readAnnotation(spec, Annotation.Table.Visibility) === "hidden";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Check if column is hidden by default */
|
|
25
|
+
export function isColumnOptional(spec: { annotations?: Annotation }): boolean {
|
|
26
|
+
return readAnnotation(spec, Annotation.Table.Visibility) === "optional";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Get effective visibility for a column, considering display config rules first, then annotations. */
|
|
30
|
+
export function getEffectiveVisibility(
|
|
31
|
+
spec: PColumnSpec,
|
|
32
|
+
displayConfig?: ColumnsDisplayOptions,
|
|
33
|
+
): undefined | "default" | "optional" | "hidden" {
|
|
34
|
+
if (displayConfig?.visibility) {
|
|
35
|
+
for (const rule of displayConfig.visibility) {
|
|
36
|
+
if (rule.match(spec)) {
|
|
37
|
+
return rule.visibility;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (isColumnHidden(spec)) return "hidden";
|
|
42
|
+
if (isColumnOptional(spec)) return "optional";
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Get ordering priority for a column. Display config rules first, then annotation fallback. */
|
|
47
|
+
export function getOrderPriority(
|
|
48
|
+
spec: PColumnSpec,
|
|
49
|
+
displayConfig?: ColumnsDisplayOptions,
|
|
50
|
+
): undefined | number {
|
|
51
|
+
if (displayConfig?.ordering) {
|
|
52
|
+
for (const rule of displayConfig.ordering) {
|
|
53
|
+
if (rule.match(spec)) {
|
|
54
|
+
return rule.priority;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return readAnnotationJson(spec, Annotation.Table.OrderPriority);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Writes derived labels into column and axis annotations.
|
|
63
|
+
* Returns new column objects with modified specs — original columns are not mutated.
|
|
64
|
+
*
|
|
65
|
+
* For each column: writes derived label into Annotation.Label (if present in derivedLabels).
|
|
66
|
+
* For each axis in column specs: writes derived axis label into AxisSpec annotations.
|
|
67
|
+
*/
|
|
68
|
+
export function withLabelAnnotations<Data>(
|
|
69
|
+
derivedLabels: undefined | Record<string, string>,
|
|
70
|
+
columns: PColumn<Data>[],
|
|
71
|
+
): PColumn<Data>[] {
|
|
72
|
+
if (derivedLabels === undefined) return columns;
|
|
73
|
+
return columns.map((col) => {
|
|
74
|
+
const colLabel = derivedLabels[col.id];
|
|
75
|
+
return {
|
|
76
|
+
...col,
|
|
77
|
+
spec: {
|
|
78
|
+
...col.spec,
|
|
79
|
+
...(isNil(colLabel)
|
|
80
|
+
? {}
|
|
81
|
+
: { annotations: { ...col.spec.annotations, [Annotation.Label]: colLabel } }),
|
|
82
|
+
axesSpec: col.spec.axesSpec.map((axis) => {
|
|
83
|
+
const label = derivedLabels[canonicalizeAxisId(axis)];
|
|
84
|
+
return isNil(label)
|
|
85
|
+
? axis
|
|
86
|
+
: { ...axis, annotations: { ...axis.annotations, [Annotation.Label]: label } };
|
|
87
|
+
}),
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Writes effective display properties (OrderPriority, Visibility) from ColumnDisplayOptions
|
|
95
|
+
* into column annotations. Returns new column objects — originals are not mutated.
|
|
96
|
+
*/
|
|
97
|
+
export function withTableVisualAnnotations<Data>(
|
|
98
|
+
displayOptions: undefined | ColumnsDisplayOptions,
|
|
99
|
+
columns: PColumn<Data>[],
|
|
100
|
+
): PColumn<Data>[] {
|
|
101
|
+
if (displayOptions === undefined) return columns;
|
|
102
|
+
return columns.map((col) => {
|
|
103
|
+
const annotations = { ...col.spec.annotations };
|
|
104
|
+
|
|
105
|
+
const visibility = getEffectiveVisibility(col.spec, displayOptions);
|
|
106
|
+
if (!isNil(visibility)) annotations[Annotation.Table.Visibility] = visibility;
|
|
107
|
+
|
|
108
|
+
const orderPriority = getOrderPriority(col.spec, displayOptions);
|
|
109
|
+
if (!isNil(orderPriority)) annotations[Annotation.Table.OrderPriority] = String(orderPriority);
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
...col,
|
|
113
|
+
spec: {
|
|
114
|
+
...col.spec,
|
|
115
|
+
annotations: annotations,
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Column shape required by label derivation. */
|
|
122
|
+
export type LabelableColumn = {
|
|
123
|
+
readonly id: PObjectId;
|
|
124
|
+
readonly spec: PColumnSpec;
|
|
125
|
+
readonly linkerPath?: { spec: PColumnSpec }[];
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/** Derive labels for all table elements: columns via deriveDistinctLabels, axes from label columns. */
|
|
129
|
+
export function deriveAllLabels(options: {
|
|
130
|
+
columns: LabelableColumn[];
|
|
131
|
+
labelColumns: { readonly spec: PColumnSpec }[];
|
|
132
|
+
deriveLabelsOptions?: DeriveLabelsOptions;
|
|
133
|
+
}): Record<string, string> {
|
|
134
|
+
const { columns, labelColumns, deriveLabelsOptions } = options;
|
|
135
|
+
const axisLabels = deriveAxisLabels(columns, labelColumns);
|
|
136
|
+
const columnLabels = deriveDistinctLabels(columns, deriveLabelsOptions).reduce(
|
|
137
|
+
(acc, label, index) => ((acc[columns[index].id] = label), acc),
|
|
138
|
+
{} as Record<string, string>,
|
|
139
|
+
);
|
|
140
|
+
return { ...axisLabels, ...columnLabels };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Derive axis labels from matching label columns, falling back to axis spec annotations. */
|
|
144
|
+
function deriveAxisLabels(
|
|
145
|
+
columns: { readonly spec: PColumnSpec }[],
|
|
146
|
+
labelColumns: { readonly spec: PColumnSpec }[],
|
|
147
|
+
): Record<string, string> {
|
|
148
|
+
const axisSpecMap = new Map(
|
|
149
|
+
columns.flatMap((c) => c.spec.axesSpec).map((a) => [canonicalizeJson(getAxisId(a)), a]),
|
|
150
|
+
);
|
|
151
|
+
const result: Record<string, string> = {};
|
|
152
|
+
for (const axisKey of axisSpecMap.keys()) {
|
|
153
|
+
const labelCol = labelColumns.find(
|
|
154
|
+
(lc) => canonicalizeJson(getAxisId(lc.spec.axesSpec[0])) === axisKey,
|
|
155
|
+
);
|
|
156
|
+
const source = labelCol?.spec ?? axisSpecMap.get(axisKey);
|
|
157
|
+
result[axisKey] = readAnnotation(source ?? {}, Annotation.Label)?.trim() ?? "Unlabeled";
|
|
158
|
+
}
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
@@ -10,6 +10,8 @@ export type {
|
|
|
10
10
|
PTableParamsV2,
|
|
11
11
|
PlDataTableStateV2Normalized,
|
|
12
12
|
PlDataTableModel,
|
|
13
|
+
PlDataTableFilterSpecLeaf,
|
|
14
|
+
PlDataTableFilterMeta,
|
|
13
15
|
PlDataTableFilters,
|
|
14
16
|
PlDataTableFiltersWithMeta,
|
|
15
17
|
} from "./typesV5";
|
|
@@ -22,10 +24,21 @@ export {
|
|
|
22
24
|
} from "./state-migration";
|
|
23
25
|
|
|
24
26
|
export { createPlDataTableSheet } from "./createPlDataTableSheet";
|
|
25
|
-
export { createPlDataTable } from "./createPlDataTable";
|
|
27
|
+
export { createPlDataTable, discoverTableColumnSnaphots } from "./createPlDataTable";
|
|
26
28
|
export { createPlDataTableV2 } from "./createPlDataTable/createPlDataTableV2";
|
|
29
|
+
export { createPlDataTableV3 } from "./createPlDataTable/createPlDataTableV3";
|
|
27
30
|
export {
|
|
28
31
|
isColumnHidden,
|
|
29
32
|
isColumnOptional,
|
|
30
|
-
|
|
33
|
+
getOrderPriority,
|
|
34
|
+
getEffectiveVisibility,
|
|
35
|
+
} from "./createPlDataTable/utils";
|
|
36
|
+
|
|
37
|
+
export type {
|
|
38
|
+
ColumnsDisplayOptions,
|
|
39
|
+
ColumnOrderRule,
|
|
40
|
+
ColumnVisibilityRule,
|
|
41
|
+
ColumnMatcher,
|
|
42
|
+
ColumnsSelectorConfig,
|
|
43
|
+
createPlDataTableOptionsV3,
|
|
31
44
|
} from "./createPlDataTable/createPlDataTableV3";
|
|
@@ -1,32 +1,43 @@
|
|
|
1
|
-
import type { AxisId, PColumn,
|
|
1
|
+
import type { AxisId, PColumn, PColumnSpec, PObjectId } from "@milaboratories/pl-model-common";
|
|
2
2
|
import {
|
|
3
3
|
getAxisId,
|
|
4
4
|
isLabelColumn,
|
|
5
5
|
matchAxisId,
|
|
6
6
|
PColumnName,
|
|
7
|
+
Services,
|
|
8
|
+
type RequireServices,
|
|
7
9
|
} from "@milaboratories/pl-model-common";
|
|
8
|
-
import type {
|
|
9
|
-
import {
|
|
10
|
+
import type { PColumnDataUniversal, RenderCtxBase } from "../../render";
|
|
11
|
+
import { ColumnCollectionBuilder, collectCtxColumnSnapshotProviders } from "../../columns";
|
|
12
|
+
import { throwError } from "@milaboratories/helpers";
|
|
10
13
|
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Get all label columns visible in the current render context
|
|
16
|
+
* (result pool + block outputs + prerun).
|
|
17
|
+
*/
|
|
18
|
+
export function getAllLabelColumns<A, U, S extends RequireServices<typeof Services.PFrameSpec>>(
|
|
19
|
+
ctx: RenderCtxBase<A, U, S>,
|
|
20
|
+
): PColumn<PColumnDataUniversal>[] {
|
|
21
|
+
const pframeSpec =
|
|
22
|
+
ctx.services.pframeSpec ?? throwError("PFrameSpec service is required for label discovery.");
|
|
23
|
+
const collection = new ColumnCollectionBuilder(pframeSpec)
|
|
24
|
+
.addSources(collectCtxColumnSnapshotProviders(ctx))
|
|
25
|
+
.build({ allowPartialColumnList: true });
|
|
26
|
+
try {
|
|
27
|
+
return collection
|
|
28
|
+
.findColumns({ include: { name: PColumnName.Label, axes: [] } })
|
|
29
|
+
.reduce<PColumn<PColumnDataUniversal>[]>((acc, hit) => {
|
|
30
|
+
const data = hit.data?.get();
|
|
31
|
+
return data === undefined ? acc : [...acc, { id: hit.id, spec: hit.spec, data }];
|
|
32
|
+
}, []);
|
|
33
|
+
} finally {
|
|
34
|
+
collection.dispose();
|
|
35
|
+
}
|
|
25
36
|
}
|
|
26
37
|
|
|
27
38
|
/** Get label columns matching the provided columns from the result pool */
|
|
28
39
|
export function getMatchingLabelColumns(
|
|
29
|
-
columns:
|
|
40
|
+
columns: { spec: PColumnSpec }[],
|
|
30
41
|
allLabelColumns: PColumn<PColumnDataUniversal>[],
|
|
31
42
|
): PColumn<PColumnDataUniversal>[] {
|
|
32
43
|
// split input columns into label and value columns
|
|
@@ -173,6 +173,7 @@ function migrateV4toV5(
|
|
|
173
173
|
gridState: entry.gridState,
|
|
174
174
|
sheetsState: entry.sheetsState,
|
|
175
175
|
filtersState,
|
|
176
|
+
defaultFiltersState: null,
|
|
176
177
|
};
|
|
177
178
|
});
|
|
178
179
|
|
|
@@ -188,8 +189,11 @@ function migrateV4toV5(
|
|
|
188
189
|
currentCache && oldSourceId
|
|
189
190
|
? {
|
|
190
191
|
sourceId: oldSourceId,
|
|
191
|
-
hiddenColIds:
|
|
192
|
+
hiddenColIds:
|
|
193
|
+
state.pTableParams.hiddenColIds?.map((id) => ({ type: "column" as const, id })) ??
|
|
194
|
+
null,
|
|
192
195
|
filters: distillFilterSpec(currentCache.filtersState),
|
|
196
|
+
defaultFilters: null,
|
|
193
197
|
sorting: state.pTableParams.sorting,
|
|
194
198
|
}
|
|
195
199
|
: createDefaultPTableParams(),
|
|
@@ -272,6 +276,7 @@ export function createDefaultPTableParams(): PTableParamsV2 {
|
|
|
272
276
|
sourceId: null,
|
|
273
277
|
hiddenColIds: null,
|
|
274
278
|
filters: null,
|
|
279
|
+
defaultFilters: null,
|
|
275
280
|
sorting: [],
|
|
276
281
|
};
|
|
277
282
|
}
|