@platforma-sdk/model 1.27.10 → 1.28.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/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +745 -602
- package/dist/index.mjs.map +1 -1
- package/dist/render/api.d.ts +19 -29
- package/dist/render/api.d.ts.map +1 -1
- package/dist/render/util/column_collection.d.ts +54 -0
- package/dist/render/util/column_collection.d.ts.map +1 -0
- package/dist/render/util/index.d.ts +1 -0
- package/dist/render/util/index.d.ts.map +1 -1
- package/dist/render/util/pcolumn_data.d.ts +11 -2
- package/dist/render/util/pcolumn_data.d.ts.map +1 -1
- package/dist/render/util/split_selectors.d.ts +26 -0
- package/dist/render/util/split_selectors.d.ts.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/render/api.ts +94 -296
- package/src/render/util/column_collection.ts +421 -0
- package/src/render/util/index.ts +1 -0
- package/src/render/util/pcolumn_data.ts +62 -4
- package/src/render/util/split_selectors.ts +29 -0
- package/dist/render/split_selectors.d.ts +0 -14
- package/dist/render/split_selectors.d.ts.map +0 -1
- package/src/render/split_selectors.ts +0 -15
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AnchoredIdDeriver,
|
|
3
|
+
AxisId,
|
|
4
|
+
DataInfo,
|
|
5
|
+
PColumn,
|
|
6
|
+
PColumnSelector,
|
|
7
|
+
PColumnSpec,
|
|
8
|
+
PObjectId,
|
|
9
|
+
SUniversalPColumnId,
|
|
10
|
+
PValue,
|
|
11
|
+
AxisFilterByIdx,
|
|
12
|
+
AnchoredPColumnSelector,
|
|
13
|
+
PartitionedDataInfoEntries,
|
|
14
|
+
} from '@milaboratories/pl-model-common';
|
|
15
|
+
import {
|
|
16
|
+
selectorsToPredicate,
|
|
17
|
+
resolveAnchors,
|
|
18
|
+
getAxisId,
|
|
19
|
+
isPColumnSpec,
|
|
20
|
+
canonicalizeAxisId,
|
|
21
|
+
isPartitionedDataInfoEntries,
|
|
22
|
+
entriesToDataInfo,
|
|
23
|
+
} from '@milaboratories/pl-model-common';
|
|
24
|
+
import type { TreeNodeAccessor } from '../accessor';
|
|
25
|
+
import type { LabelDerivationOps, TraceEntry } from './label';
|
|
26
|
+
import { deriveLabels } from './label';
|
|
27
|
+
import type { Optional } from 'utility-types';
|
|
28
|
+
import type { APColumnSelectorWithSplit, PColumnSelectorWithSplit } from './split_selectors';
|
|
29
|
+
import canonicalize from 'canonicalize';
|
|
30
|
+
import { getUniquePartitionKeys, convertOrParsePColumnData } from './pcolumn_data';
|
|
31
|
+
import { filterDataInfoEntries } from './axis_filtering';
|
|
32
|
+
|
|
33
|
+
export interface ColumnProvider {
|
|
34
|
+
selectColumns(selectors: ((spec: PColumnSpec) => boolean) | PColumnSelector | PColumnSelector[]): PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor> | undefined>[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface AxisLabelProvider {
|
|
38
|
+
findLabels(axis: AxisId): Record<string | number, string> | undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A simple implementation of {@link ColumnProvider} backed by a pre-defined array of columns.
|
|
43
|
+
*/
|
|
44
|
+
class ArrayColumnProvider implements ColumnProvider {
|
|
45
|
+
constructor(private readonly columns: PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor> | undefined>[]) {}
|
|
46
|
+
|
|
47
|
+
selectColumns(selectors: ((spec: PColumnSpec) => boolean) | PColumnSelector | PColumnSelector[]):
|
|
48
|
+
PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor> | undefined>[] {
|
|
49
|
+
const predicate = typeof selectors === 'function' ? selectors : selectorsToPredicate(selectors);
|
|
50
|
+
// Filter based on spec, ignoring data type for now
|
|
51
|
+
return this.columns.filter((column): column is PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor> | undefined> => predicate(column.spec));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type PColumnEntryWithLabel = {
|
|
56
|
+
id: PObjectId;
|
|
57
|
+
spec: PColumnSpec;
|
|
58
|
+
/** Lazy calculates the data, returns undefined if data is not ready. */
|
|
59
|
+
data(): DataInfo<TreeNodeAccessor> | TreeNodeAccessor | undefined;
|
|
60
|
+
label: string;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/** Universal column is a column that uses a universal column id, and always have label. */
|
|
64
|
+
export type PColumnEntryUniversal = PColumnEntryWithLabel & {
|
|
65
|
+
id: SUniversalPColumnId;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// Helper types similar to those in api.ts
|
|
69
|
+
type AxisFilterInfo = {
|
|
70
|
+
axisIdx: number;
|
|
71
|
+
axisId: AxisId;
|
|
72
|
+
value: PValue;
|
|
73
|
+
label: string;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Intermediate representation for columns requiring splitting
|
|
77
|
+
type IntermediateSplitEntry = {
|
|
78
|
+
type: 'split';
|
|
79
|
+
originalColumn: PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor> | undefined>;
|
|
80
|
+
spec: PColumnSpec;
|
|
81
|
+
/** With splitting axes removed */
|
|
82
|
+
adjustedSpec: PColumnSpec;
|
|
83
|
+
dataEntries: PartitionedDataInfoEntries<TreeNodeAccessor>;
|
|
84
|
+
axisFilters: AxisFilterInfo[];
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Intermediate representation for columns NOT requiring splitting
|
|
88
|
+
type IntermediateDirectEntry = {
|
|
89
|
+
type: 'direct';
|
|
90
|
+
originalColumn: PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor> | undefined>;
|
|
91
|
+
spec: PColumnSpec;
|
|
92
|
+
/** The same as `spec` */
|
|
93
|
+
adjustedSpec: PColumnSpec;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Union type for intermediate processing
|
|
97
|
+
type IntermediateColumnEntry = IntermediateSplitEntry | IntermediateDirectEntry;
|
|
98
|
+
|
|
99
|
+
function splitFiltersToTrace(splitFilters?: AxisFilterInfo[]): TraceEntry[] | undefined {
|
|
100
|
+
if (!splitFilters) return undefined;
|
|
101
|
+
return splitFilters.map((filter) => ({
|
|
102
|
+
type: `split:${canonicalizeAxisId(filter.axisId)}`,
|
|
103
|
+
label: filter.label,
|
|
104
|
+
importance: 1_000_000, // High importance for split filters in labels
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function splitFiltersToAxisFilter(splitFilters?: AxisFilterInfo[]): AxisFilterByIdx[] | undefined {
|
|
109
|
+
if (!splitFilters) return undefined;
|
|
110
|
+
return splitFilters.map((filter): AxisFilterByIdx => [filter.axisIdx, filter.value]);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function fallbackIdDeriver(originalId: PObjectId, axisFilters?: AxisFilterByIdx[]): PObjectId {
|
|
114
|
+
if (!axisFilters || axisFilters.length === 0) return originalId;
|
|
115
|
+
const filtersToCanonicalize = [...axisFilters].sort((a, b) => a[0] - b[0]);
|
|
116
|
+
return canonicalize({ id: originalId, axisFilters: filtersToCanonicalize })! as PObjectId;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Checks if a selector object uses any anchor properties */
|
|
120
|
+
function hasAnchors(selector: unknown): selector is AnchoredPColumnSelector {
|
|
121
|
+
if (!selector || typeof selector !== 'object') return false;
|
|
122
|
+
const potentialAnchored = selector as Record<string, any>;
|
|
123
|
+
const domainHasAnchors = potentialAnchored['domain'] && typeof potentialAnchored['domain'] === 'object' && Object.values(potentialAnchored['domain']).some((v: unknown) => typeof v === 'object' && v !== null && 'anchor' in v);
|
|
124
|
+
const axesHaveAnchors = potentialAnchored['axes'] && Array.isArray(potentialAnchored['axes']) && potentialAnchored['axes'].some((a: unknown) => typeof a === 'object' && a !== null && 'anchor' in a);
|
|
125
|
+
return !!potentialAnchored['domainAnchor'] || domainHasAnchors || axesHaveAnchors;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Derives the indices of axes marked for splitting based on the selector.
|
|
130
|
+
* Throws an error if splitting is requested alongside `partialAxesMatch`.
|
|
131
|
+
*/
|
|
132
|
+
function getSplitAxisIndices(selector: APColumnSelectorWithSplit | ((spec: PColumnSpec) => boolean)): number[] {
|
|
133
|
+
if (typeof selector !== 'object' || !('axes' in selector) || selector.axes === undefined) {
|
|
134
|
+
return []; // No axes specified or not an object selector, no splitting
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const splitIndices = selector.axes
|
|
138
|
+
.map((axis, index) => (typeof axis === 'object' && 'split' in axis && axis.split === true) ? index : -1)
|
|
139
|
+
.filter((index) => index !== -1);
|
|
140
|
+
|
|
141
|
+
if (splitIndices.length > 0 && selector.partialAxesMatch !== undefined) {
|
|
142
|
+
throw new Error('Axis splitting is not supported when `partialAxesMatch` is defined.');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
splitIndices.sort((a, b) => a - b);
|
|
146
|
+
return splitIndices;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
type UniversalPColumnOptsNoDeriver = {
|
|
150
|
+
labelOps?: LabelDerivationOps;
|
|
151
|
+
/** If true, incomplete data will cause the column to be skipped instead of returning undefined for the whole request. */
|
|
152
|
+
dontWaitAllData?: boolean;
|
|
153
|
+
/**
|
|
154
|
+
* If true, the derived label will override the 'pl7.app/label' annotation
|
|
155
|
+
* in the resulting PColumnSpec. It also forces `includeNativeLabel` in `labelOps` to true,
|
|
156
|
+
* unless `labelOps.includeNativeLabel` is explicitly set to false.
|
|
157
|
+
* Default value in getUniversalEntries is false, in getColumns it is true.
|
|
158
|
+
*/
|
|
159
|
+
overrideLabelAnnotation?: boolean;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
type UniversalPColumnOpts = UniversalPColumnOptsNoDeriver & {
|
|
163
|
+
anchorCtx: AnchoredIdDeriver;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export class PColumnCollection {
|
|
167
|
+
private readonly defaultProviderStore: PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor> | undefined>[] = [];
|
|
168
|
+
private readonly providers: ColumnProvider[] = [new ArrayColumnProvider(this.defaultProviderStore)];
|
|
169
|
+
private readonly axisLabelProviders: AxisLabelProvider[] = [];
|
|
170
|
+
|
|
171
|
+
constructor() {}
|
|
172
|
+
|
|
173
|
+
public addColumnProvider(provider: ColumnProvider): this {
|
|
174
|
+
this.providers.push(provider);
|
|
175
|
+
return this;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
public addAxisLabelProvider(provider: AxisLabelProvider): this {
|
|
179
|
+
this.axisLabelProviders.push(provider);
|
|
180
|
+
return this;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
public addColumns(columns: PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor> | undefined>[]): this {
|
|
184
|
+
this.defaultProviderStore.push(...columns);
|
|
185
|
+
return this;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
public addColumn(column: PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor> | undefined>): this {
|
|
189
|
+
this.defaultProviderStore.push(column);
|
|
190
|
+
return this;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Fetches labels for a given axis from the registered providers */
|
|
194
|
+
private findLabels(axis: AxisId): Record<string | number, string> | undefined {
|
|
195
|
+
for (const provider of this.axisLabelProviders) {
|
|
196
|
+
const labels = provider.findLabels(axis);
|
|
197
|
+
if (labels) return labels; // First provider wins
|
|
198
|
+
}
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Overload signatures updated to return PColumnEntry types
|
|
203
|
+
public getUniversalEntries(
|
|
204
|
+
predicateOrSelectors: ((spec: PColumnSpec) => boolean) | APColumnSelectorWithSplit | APColumnSelectorWithSplit[],
|
|
205
|
+
opts: UniversalPColumnOpts): PColumnEntryUniversal[] | undefined;
|
|
206
|
+
public getUniversalEntries(
|
|
207
|
+
predicateOrSelectors: ((spec: PColumnSpec) => boolean) | PColumnSelectorWithSplit | PColumnSelectorWithSplit[],
|
|
208
|
+
opts?: UniversalPColumnOptsNoDeriver): PColumnEntryWithLabel[] | undefined;
|
|
209
|
+
public getUniversalEntries(
|
|
210
|
+
predicateOrSelectors: ((spec: PColumnSpec) => boolean) | APColumnSelectorWithSplit | APColumnSelectorWithSplit[],
|
|
211
|
+
opts?: Optional<UniversalPColumnOpts, 'anchorCtx'>): (PColumnEntryWithLabel | PColumnEntryUniversal)[] | undefined {
|
|
212
|
+
const { anchorCtx, labelOps: rawLabelOps, dontWaitAllData = false, overrideLabelAnnotation = false } = opts ?? {};
|
|
213
|
+
|
|
214
|
+
const labelOps: LabelDerivationOps = {
|
|
215
|
+
...(overrideLabelAnnotation && rawLabelOps?.includeNativeLabel !== false ? { includeNativeLabel: true } : {}),
|
|
216
|
+
...(rawLabelOps ?? {}),
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const selectorsArray = typeof predicateOrSelectors === 'function'
|
|
220
|
+
? [predicateOrSelectors]
|
|
221
|
+
: Array.isArray(predicateOrSelectors)
|
|
222
|
+
? predicateOrSelectors
|
|
223
|
+
: [predicateOrSelectors];
|
|
224
|
+
|
|
225
|
+
const intermediateResults: IntermediateColumnEntry[] = [];
|
|
226
|
+
|
|
227
|
+
for (const rawSelector of selectorsArray) {
|
|
228
|
+
const usesAnchors = hasAnchors(rawSelector);
|
|
229
|
+
|
|
230
|
+
let currentSelector: PColumnSelectorWithSplit | ((spec: PColumnSpec) => boolean);
|
|
231
|
+
if (usesAnchors) {
|
|
232
|
+
if (!anchorCtx)
|
|
233
|
+
throw new Error('Anchored selectors require an AnchoredIdDeriver to be provided in options.');
|
|
234
|
+
currentSelector = resolveAnchors(anchorCtx.anchors, rawSelector as AnchoredPColumnSelector);
|
|
235
|
+
} else
|
|
236
|
+
currentSelector = rawSelector as PColumnSelectorWithSplit | ((spec: PColumnSpec) => boolean);
|
|
237
|
+
|
|
238
|
+
const selectedIds = new Set<PObjectId>();
|
|
239
|
+
const selectedColumns: PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor> | undefined>[] = [];
|
|
240
|
+
for (const provider of this.providers) {
|
|
241
|
+
const providerColumns = provider.selectColumns(currentSelector);
|
|
242
|
+
for (const col of providerColumns) {
|
|
243
|
+
if (selectedIds.has(col.id)) throw new Error(`Duplicate column id ${col.id} in provider ${provider.constructor.name}`);
|
|
244
|
+
selectedIds.add(col.id);
|
|
245
|
+
selectedColumns.push(col);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (selectedColumns.length === 0) continue;
|
|
250
|
+
|
|
251
|
+
const splitAxisIdxs = getSplitAxisIndices(rawSelector);
|
|
252
|
+
const needsSplitting = splitAxisIdxs.length > 0;
|
|
253
|
+
|
|
254
|
+
for (const column of selectedColumns) {
|
|
255
|
+
if (!isPColumnSpec(column.spec)) continue;
|
|
256
|
+
|
|
257
|
+
const originalSpec = column.spec;
|
|
258
|
+
|
|
259
|
+
if (needsSplitting) {
|
|
260
|
+
const dataEntries = convertOrParsePColumnData(column.data);
|
|
261
|
+
|
|
262
|
+
if (!dataEntries) {
|
|
263
|
+
if (dontWaitAllData) continue;
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (!isPartitionedDataInfoEntries(dataEntries))
|
|
268
|
+
throw new Error(`Splitting requires Partitioned DataInfoEntries, but parsing resulted in ${dataEntries.type} for column ${column.id}`);
|
|
269
|
+
|
|
270
|
+
const uniqueKeys = getUniquePartitionKeys(dataEntries);
|
|
271
|
+
|
|
272
|
+
const maxSplitIdx = splitAxisIdxs[splitAxisIdxs.length - 1];
|
|
273
|
+
if (maxSplitIdx >= dataEntries.partitionKeyLength)
|
|
274
|
+
throw new Error(`Not enough partition keys (${dataEntries.partitionKeyLength}) for requested split axes (max index ${maxSplitIdx}) in column ${originalSpec.name}`);
|
|
275
|
+
|
|
276
|
+
const axesLabels: (Record<string | number, string> | undefined)[] = splitAxisIdxs
|
|
277
|
+
.map((idx) => this.findLabels(getAxisId(originalSpec.axesSpec[idx])));
|
|
278
|
+
|
|
279
|
+
const keyCombinations: (string | number)[][] = [];
|
|
280
|
+
const generateCombinations = (currentCombo: (string | number)[], sAxisIdx: number) => {
|
|
281
|
+
if (sAxisIdx >= splitAxisIdxs.length) {
|
|
282
|
+
keyCombinations.push([...currentCombo]);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const axisIdx = splitAxisIdxs[sAxisIdx];
|
|
286
|
+
if (axisIdx >= uniqueKeys.length)
|
|
287
|
+
throw new Error(`Axis index ${axisIdx} out of bounds for unique keys array (length ${uniqueKeys.length}) during split key generation for column ${column.id}`);
|
|
288
|
+
const axisValues = uniqueKeys[axisIdx];
|
|
289
|
+
if (!axisValues || axisValues.length === 0) {
|
|
290
|
+
keyCombinations.length = 0; // No combinations possible if one axis has no keys
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
for (const val of axisValues) {
|
|
294
|
+
currentCombo.push(val);
|
|
295
|
+
generateCombinations(currentCombo, sAxisIdx + 1);
|
|
296
|
+
currentCombo.pop();
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
generateCombinations([], 0);
|
|
301
|
+
|
|
302
|
+
if (keyCombinations.length === 0)
|
|
303
|
+
continue;
|
|
304
|
+
|
|
305
|
+
const newAxesSpec = [...originalSpec.axesSpec];
|
|
306
|
+
const splitAxisOriginalIdxs = splitAxisIdxs.map((idx) => idx); // Keep original indices for axisId lookup
|
|
307
|
+
// Remove axes in reverse order to maintain correct indices during removal
|
|
308
|
+
for (let i = splitAxisIdxs.length - 1; i >= 0; i--) {
|
|
309
|
+
newAxesSpec.splice(splitAxisIdxs[i], 1);
|
|
310
|
+
}
|
|
311
|
+
const adjustedSpec = { ...originalSpec, axesSpec: newAxesSpec };
|
|
312
|
+
|
|
313
|
+
for (const keyCombo of keyCombinations) {
|
|
314
|
+
const splitFilters: AxisFilterInfo[] = keyCombo.map((value, sAxisIdx) => {
|
|
315
|
+
const axisIdx = splitAxisOriginalIdxs[sAxisIdx]; // Use original index for lookup
|
|
316
|
+
const axisId = getAxisId(originalSpec.axesSpec[axisIdx]);
|
|
317
|
+
const axisLabelMap = axesLabels[sAxisIdx];
|
|
318
|
+
const label = axisLabelMap?.[value] ?? String(value);
|
|
319
|
+
return { axisIdx, axisId, value: value as PValue, label };
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
intermediateResults.push({
|
|
323
|
+
type: 'split',
|
|
324
|
+
originalColumn: column,
|
|
325
|
+
spec: originalSpec,
|
|
326
|
+
adjustedSpec,
|
|
327
|
+
dataEntries,
|
|
328
|
+
axisFilters: splitFilters,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
intermediateResults.push({
|
|
333
|
+
type: 'direct',
|
|
334
|
+
originalColumn: column,
|
|
335
|
+
spec: originalSpec,
|
|
336
|
+
adjustedSpec: originalSpec,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (intermediateResults.length === 0) return [];
|
|
343
|
+
|
|
344
|
+
const labeledResults = deriveLabels(
|
|
345
|
+
intermediateResults,
|
|
346
|
+
(entry) => ({
|
|
347
|
+
spec: entry.spec,
|
|
348
|
+
suffixTrace: entry.type === 'split' ? splitFiltersToTrace(entry.axisFilters) : undefined,
|
|
349
|
+
}),
|
|
350
|
+
labelOps,
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
const result: (PColumnEntryWithLabel | PColumnEntryUniversal)[] = [];
|
|
354
|
+
|
|
355
|
+
for (const { value: entry, label } of labeledResults) {
|
|
356
|
+
const { originalColumn, spec: originalSpec } = entry;
|
|
357
|
+
|
|
358
|
+
const axisFilters = entry.type === 'split' ? entry.axisFilters : undefined;
|
|
359
|
+
const axisFiltersTuple = splitFiltersToAxisFilter(axisFilters);
|
|
360
|
+
|
|
361
|
+
let finalId: SUniversalPColumnId | PObjectId;
|
|
362
|
+
if (anchorCtx) finalId = anchorCtx.deriveS(originalSpec, axisFiltersTuple);
|
|
363
|
+
else finalId = fallbackIdDeriver(originalColumn.id, axisFiltersTuple);
|
|
364
|
+
|
|
365
|
+
let finalSpec = { ...entry.adjustedSpec };
|
|
366
|
+
|
|
367
|
+
if (overrideLabelAnnotation) {
|
|
368
|
+
finalSpec = {
|
|
369
|
+
...finalSpec,
|
|
370
|
+
annotations: {
|
|
371
|
+
...(finalSpec.annotations ?? {}),
|
|
372
|
+
'pl7.app/label': label,
|
|
373
|
+
},
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
result.push({
|
|
378
|
+
id: finalId,
|
|
379
|
+
spec: finalSpec,
|
|
380
|
+
data: () => entry.type === 'split'
|
|
381
|
+
? entriesToDataInfo(filterDataInfoEntries(entry.dataEntries, axisFiltersTuple!))
|
|
382
|
+
: entry.originalColumn.data,
|
|
383
|
+
label: label,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return result;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
public getColumns(
|
|
391
|
+
predicateOrSelectors: ((spec: PColumnSpec) => boolean) | APColumnSelectorWithSplit | APColumnSelectorWithSplit[],
|
|
392
|
+
opts: UniversalPColumnOpts): PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor>>[] | undefined;
|
|
393
|
+
public getColumns(
|
|
394
|
+
predicateOrSelectors: ((spec: PColumnSpec) => boolean) | PColumnSelectorWithSplit | PColumnSelectorWithSplit[],
|
|
395
|
+
opts?: UniversalPColumnOptsNoDeriver): PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor>>[] | undefined;
|
|
396
|
+
public getColumns(
|
|
397
|
+
predicateOrSelectors: ((spec: PColumnSpec) => boolean) | APColumnSelectorWithSplit | APColumnSelectorWithSplit[],
|
|
398
|
+
opts?: Optional<UniversalPColumnOpts, 'anchorCtx'>): PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor>>[] | undefined {
|
|
399
|
+
const entries = this.getUniversalEntries(predicateOrSelectors, {
|
|
400
|
+
overrideLabelAnnotation: true, // default for getColumns
|
|
401
|
+
...(opts ?? {}),
|
|
402
|
+
} as UniversalPColumnOpts);
|
|
403
|
+
if (!entries) return undefined;
|
|
404
|
+
|
|
405
|
+
const columns: PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor>>[] = [];
|
|
406
|
+
for (const entry of entries) {
|
|
407
|
+
const data = entry.data();
|
|
408
|
+
if (!data) {
|
|
409
|
+
if (opts?.dontWaitAllData) continue;
|
|
410
|
+
return undefined;
|
|
411
|
+
}
|
|
412
|
+
columns.push({
|
|
413
|
+
id: entry.id,
|
|
414
|
+
spec: entry.spec,
|
|
415
|
+
data,
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return columns;
|
|
420
|
+
}
|
|
421
|
+
}
|
package/src/render/util/index.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import type {
|
|
2
|
+
DataInfo } from '@milaboratories/pl-model-common';
|
|
2
3
|
import {
|
|
4
|
+
dataInfoToEntries,
|
|
5
|
+
isDataInfo,
|
|
6
|
+
isDataInfoEntries,
|
|
3
7
|
type BinaryChunk,
|
|
4
8
|
type BinaryPartitionedDataInfoEntries,
|
|
5
9
|
type DataInfoEntries,
|
|
@@ -7,7 +11,7 @@ import {
|
|
|
7
11
|
type PColumnDataEntry,
|
|
8
12
|
type PColumnKey,
|
|
9
13
|
} from '@milaboratories/pl-model-common';
|
|
10
|
-
import
|
|
14
|
+
import { TreeNodeAccessor } from '../accessor';
|
|
11
15
|
|
|
12
16
|
const PCD_PREFIX = 'PColumnData/';
|
|
13
17
|
|
|
@@ -182,11 +186,46 @@ export function getPartitionKeysList(
|
|
|
182
186
|
return { data, keyLength };
|
|
183
187
|
}
|
|
184
188
|
|
|
189
|
+
function getUniquePartitionKeysForDataEntries(list: DataInfoEntries<unknown>): (string | number)[][] {
|
|
190
|
+
if (list.type !== 'JsonPartitioned' && list.type !== 'BinaryPartitioned')
|
|
191
|
+
throw new Error(`Splitting requires Partitioned DataInfoEntries, got ${list.type}`);
|
|
192
|
+
|
|
193
|
+
const { parts, partitionKeyLength } = list;
|
|
194
|
+
|
|
195
|
+
const result: Set<string | number>[] = [];
|
|
196
|
+
for (let i = 0; i < partitionKeyLength; ++i) {
|
|
197
|
+
result.push(new Set());
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
for (const part of parts) {
|
|
201
|
+
const key = part.key;
|
|
202
|
+
if (key.length !== partitionKeyLength) {
|
|
203
|
+
throw new Error(
|
|
204
|
+
`Key length (${key.length}) does not match partition length (${partitionKeyLength}) for key: ${JSON.stringify(
|
|
205
|
+
key,
|
|
206
|
+
)}`,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
for (let i = 0; i < partitionKeyLength; ++i) {
|
|
210
|
+
result[i].add(key[i]);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return result.map((s) => Array.from(s.values()));
|
|
215
|
+
}
|
|
216
|
+
|
|
185
217
|
/** Returns an array of unique partition keys for each column: the i-th element in the resulting 2d array contains all unique values of i-th partition axis. */
|
|
186
|
-
|
|
218
|
+
export function getUniquePartitionKeys(acc: undefined): undefined;
|
|
219
|
+
export function getUniquePartitionKeys(acc: DataInfoEntries<unknown>): (string | number)[][];
|
|
220
|
+
export function getUniquePartitionKeys(acc: TreeNodeAccessor): (string | number)[][] | undefined;
|
|
187
221
|
export function getUniquePartitionKeys(
|
|
188
|
-
acc: TreeNodeAccessor | undefined,
|
|
222
|
+
acc: TreeNodeAccessor | DataInfoEntries<unknown> | undefined,
|
|
189
223
|
): (string | number)[][] | undefined {
|
|
224
|
+
if (acc === undefined) return undefined;
|
|
225
|
+
|
|
226
|
+
if (isDataInfoEntries(acc))
|
|
227
|
+
return getUniquePartitionKeysForDataEntries(acc);
|
|
228
|
+
|
|
190
229
|
const list = getPartitionKeysList(acc);
|
|
191
230
|
if (!list) return undefined;
|
|
192
231
|
|
|
@@ -225,6 +264,8 @@ export function parsePColumnData(
|
|
|
225
264
|
): JsonPartitionedDataInfoEntries<TreeNodeAccessor> | BinaryPartitionedDataInfoEntries<TreeNodeAccessor> | undefined {
|
|
226
265
|
if (acc === undefined) return undefined;
|
|
227
266
|
|
|
267
|
+
if (!acc.getIsReadyOrError()) return undefined;
|
|
268
|
+
|
|
228
269
|
const resourceType = acc.resourceType.name;
|
|
229
270
|
const meta = acc.getDataAsJson<Record<string, number>>();
|
|
230
271
|
|
|
@@ -385,3 +426,20 @@ export function parsePColumnData(
|
|
|
385
426
|
throw new Error(`Unknown resource type: ${resourceType}`);
|
|
386
427
|
}
|
|
387
428
|
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Converts or parses the input into DataInfoEntries format.
|
|
432
|
+
|
|
433
|
+
* @param acc - The input data, which can be TreeNodeAccessor, DataInfoEntries, DataInfo, or undefined.
|
|
434
|
+
* @returns The data in DataInfoEntries format, or undefined if the input was undefined or data is not ready.
|
|
435
|
+
*/
|
|
436
|
+
export function convertOrParsePColumnData(acc: TreeNodeAccessor | DataInfoEntries<TreeNodeAccessor> | DataInfo<TreeNodeAccessor> | undefined):
|
|
437
|
+
DataInfoEntries<TreeNodeAccessor> | undefined {
|
|
438
|
+
if (acc === undefined) return undefined;
|
|
439
|
+
|
|
440
|
+
if (isDataInfoEntries(acc)) return acc;
|
|
441
|
+
if (isDataInfo(acc)) return dataInfoToEntries(acc);
|
|
442
|
+
if (acc instanceof TreeNodeAccessor) return parsePColumnData(acc);
|
|
443
|
+
|
|
444
|
+
throw new Error(`Unexpected input type: ${typeof acc}`);
|
|
445
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { AAxisSelector, AnchoredPColumnSelector, AxisSelector, PColumnSelector } from '@milaboratories/pl-model-common';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AAxisSelector with an optional split flag
|
|
5
|
+
*/
|
|
6
|
+
export type AAxisSelectorWithSplit = AAxisSelector & {
|
|
7
|
+
split?: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* APColumnSelector with an optional split flag for each axis
|
|
12
|
+
*/
|
|
13
|
+
export type APColumnSelectorWithSplit = AnchoredPColumnSelector & {
|
|
14
|
+
axes?: AAxisSelectorWithSplit[];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* AxisSelector with an optional split flag
|
|
19
|
+
*/
|
|
20
|
+
export type AxisSelectorWithSplit = AxisSelector & {
|
|
21
|
+
split?: boolean;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* PColumnSelector with an optional split flag for each axis
|
|
26
|
+
*/
|
|
27
|
+
export type PColumnSelectorWithSplit = PColumnSelector & {
|
|
28
|
+
axes?: AxisSelectorWithSplit[];
|
|
29
|
+
};
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { AAxisSelector, AnchoredPColumnSelector } from '@milaboratories/pl-model-common';
|
|
2
|
-
/**
|
|
3
|
-
* AAxisSelector with an optional split flag
|
|
4
|
-
*/
|
|
5
|
-
export type AAxisSelectorWithSplit = AAxisSelector & {
|
|
6
|
-
split?: boolean;
|
|
7
|
-
};
|
|
8
|
-
/**
|
|
9
|
-
* APColumnSelector with an optional split flag for each axis
|
|
10
|
-
*/
|
|
11
|
-
export type APColumnSelectorWithSplit = AnchoredPColumnSelector & {
|
|
12
|
-
axes?: AAxisSelectorWithSplit[];
|
|
13
|
-
};
|
|
14
|
-
//# sourceMappingURL=split_selectors.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"split_selectors.d.ts","sourceRoot":"","sources":["../../src/render/split_selectors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAE9F;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,aAAa,GAAG;IACnD,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,yBAAyB,GAAG,uBAAuB,GAAG;IAChE,IAAI,CAAC,EAAE,sBAAsB,EAAE,CAAC;CACjC,CAAC"}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { AAxisSelector, AnchoredPColumnSelector } from '@milaboratories/pl-model-common';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* AAxisSelector with an optional split flag
|
|
5
|
-
*/
|
|
6
|
-
export type AAxisSelectorWithSplit = AAxisSelector & {
|
|
7
|
-
split?: boolean;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* APColumnSelector with an optional split flag for each axis
|
|
12
|
-
*/
|
|
13
|
-
export type APColumnSelectorWithSplit = AnchoredPColumnSelector & {
|
|
14
|
-
axes?: AAxisSelectorWithSplit[];
|
|
15
|
-
};
|