@platforma-sdk/model 1.33.8 → 1.33.14

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.
@@ -1,11 +1,8 @@
1
1
  import type {
2
- AxisId,
3
- CanonicalizedJson,
2
+ AxisId, CanonicalizedJson,
4
3
  DataInfo,
5
4
  PColumn,
6
- PColumnIdAndSpec,
7
5
  PColumnSpec,
8
- PColumnSpecId,
9
6
  PColumnValues,
10
7
  PFrameHandle,
11
8
  PObjectId,
@@ -13,13 +10,11 @@ import type {
13
10
  import {
14
11
  canonicalizeJson,
15
12
  getAxisId,
16
- getPColumnSpecId,
17
- isPColumn,
18
- matchAxisId,
19
- parseJson,
13
+ matchAxisId, parseJson,
20
14
  } from '@milaboratories/pl-model-common';
21
- import type { RenderCtx } from '../render';
22
- import { TreeNodeAccessor } from '../render';
15
+ import type { PColumnDataUniversal, RenderCtx } from '../render';
16
+ import { PColumnCollection, TreeNodeAccessor } from '../render';
17
+ import { isLabelColumn } from './PlDataTable';
23
18
 
24
19
  /** Create id for column copy with added keys in axes domains */
25
20
  const colId = (id: PObjectId, domains: (Record<string, string> | undefined)[]) => {
@@ -56,104 +51,92 @@ export function isLinkerColumn(column: PColumnSpec) {
56
51
  return column.axesSpec.length === 2 && column.annotations?.[LINKER_COLUMN_ANNOTATION] === 'true';
57
52
  }
58
53
 
59
- export type LinkerColumnsMap = Map<CanonicalizedJson<AxisId>, Map<CanonicalizedJson<AxisId>, PColumnIdAndSpec>>;
60
- export function getLinkerColumnsMap(linkerColumns: PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor> | PColumnValues>[]) {
54
+ export const IS_VIRTUAL_COLUMN = 'pl7.app/graph/isVirtual'; // annotation for column duplicates with extended domains
55
+ export const LABEL_ANNOTATION = 'pl7.app/label';
56
+ export const LINKER_COLUMN_ANNOTATION = 'pl7.app/isLinkerColumn';
57
+
58
+ export type LinkerColumnsMap = Map<CanonicalizedJson<AxisId>, Set<CanonicalizedJson<AxisId>>>;
59
+ export function getLinkerColumnsMap(linkerColumns: PColumn<PColumnDataUniversal>[]) {
61
60
  const resultMap: LinkerColumnsMap = new Map();
62
- for (const { id, spec } of linkerColumns) {
63
- const [idA, idB] = spec.axesSpec.map(getAxisId).map(canonicalizeJson);
64
- if (!resultMap.has(idA)) {
65
- resultMap.set(idA, new Map());
66
- }
67
- if (!resultMap.has(idB)) {
68
- resultMap.set(idB, new Map());
61
+ for (const { spec } of linkerColumns) {
62
+ const axisIds = spec.axesSpec.map(getAxisId).map(canonicalizeJson);
63
+ axisIds.forEach((id) => {
64
+ if (!resultMap.has(id)) {
65
+ resultMap.set(id, new Set());
66
+ }
67
+ });
68
+ for (let i = 0; i < axisIds.length - 1; i++) {
69
+ for (let j = i + 1; j < axisIds.length; j++) {
70
+ const id1 = axisIds[i];
71
+ const id2 = axisIds[j];
72
+ resultMap.get(id1)?.add(id2);
73
+ resultMap.get(id2)?.add(id1);
74
+ }
69
75
  }
70
- resultMap.get(idA)?.set(idB, { columnId: id, spec })
71
- resultMap.get(idB)?.set(idA, { columnId: id, spec })
72
76
  }
73
77
  return resultMap;
74
78
  }
75
79
 
76
- export function hasPathWithLinkerColumns(
77
- linkerColumnsMap: LinkerColumnsMap,
78
- startId: AxisId,
79
- endId: AxisId,
80
- ): boolean {
80
+ export function getAvailableWithLinkersAxes(
81
+ linkerColumns: PColumn<PColumnDataUniversal>[],
82
+ blockAxes: Map<CanonicalizedJson<AxisId>, AxisId>,
83
+ ): Map<CanonicalizedJson<AxisId>, AxisId> {
84
+ const linkerColumnsMap = getLinkerColumnsMap(linkerColumns);
81
85
  const linkerColumnsMapIds = [...linkerColumnsMap.keys()].map(parseJson);
82
- const startIdMatched = linkerColumnsMapIds.find((id) => matchAxisId(startId, id));
83
- if (!startIdMatched) return false;
84
-
85
- const startKey = canonicalizeJson(getAxisId(startIdMatched));
86
- const visited = new Set([startKey]);
87
- let nextKeys = [startKey];
86
+ const startKeys: CanonicalizedJson<AxisId>[] = [];
87
+ for (const startId of blockAxes.values()) {
88
+ const matched = linkerColumnsMapIds.find((id) => matchAxisId(startId, id));
89
+ if (matched) {
90
+ startKeys.push(canonicalizeJson(matched)); // linker column can contain fewer domains than in block's columns, it's fixed on next step in enrichCompatible
91
+ }
92
+ }
93
+ const visited: Set<CanonicalizedJson<AxisId>> = new Set(startKeys);
94
+ const addedAvailableAxes: Map<CanonicalizedJson<AxisId>, AxisId> = new Map();
95
+ let nextKeys = [...startKeys];
88
96
 
89
97
  while (nextKeys.length) {
90
98
  const next: CanonicalizedJson<AxisId>[] = [];
91
99
  for (const nextKey of nextKeys) {
92
- for (const availableKey of linkerColumnsMap.get(nextKey)?.keys() ?? []) {
93
- const availableId = parseJson(availableKey);
94
- if (matchAxisId(endId, availableId)) return true;
100
+ for (const availableKey of linkerColumnsMap.get(nextKey) ?? []) {
95
101
  if (!visited.has(availableKey)) {
96
102
  next.push(availableKey);
97
103
  visited.add(availableKey);
104
+ addedAvailableAxes.set(availableKey, parseJson(availableKey));
98
105
  }
99
106
  }
100
107
  }
101
108
  nextKeys = next;
102
109
  }
103
- return false;
104
- }
105
-
106
- /** Check if axes of secondary column are exactly in axes of main column */
107
- function checkFullCompatibility(
108
- mainColumn: PColumnSpec,
109
- secondaryColumn: PColumnSpec,
110
- linkerColumnsMap?: LinkerColumnsMap,
111
- ): boolean {
112
- const mainAxesIds = mainColumn.axesSpec.map(getAxisId);
113
- const secondaryAxesIds = secondaryColumn.axesSpec.map(getAxisId);
114
- // with fixed axes (sliced columns) in data-mapping there is enough to have only one axis in intersection
115
- return secondaryAxesIds.some((id) => mainAxesIds.some((mainId) => matchAxisId(mainId, id) && matchAxisId(id, mainId)))
116
- || (!!linkerColumnsMap && secondaryAxesIds.some((id) => mainAxesIds.some((mainId) => hasPathWithLinkerColumns(linkerColumnsMap, mainId, id))));
110
+ return addedAvailableAxes;
117
111
  }
118
-
119
- /** Check if axes of secondary column are in axes of main column, but they can have compatible difference in domains */
120
- function checkCompatibility(
121
- mainColumn: PColumnSpec,
122
- secondaryColumn: PColumnSpec,
123
- linkerColumnsMap?: LinkerColumnsMap,
124
- ): boolean {
125
- const mainAxesIds = mainColumn.axesSpec.map(getAxisId);
126
- const secondaryAxesIds = secondaryColumn.axesSpec.map(getAxisId);
127
- // with fixed axes (sliced columns) in data-mapping there is enough to have only one axis in intersection
128
- return secondaryAxesIds.some((id) => mainAxesIds.some((mainId) => matchAxisId(mainId, id)))
129
- || (!!linkerColumnsMap && secondaryAxesIds.some((id) => mainAxesIds.some((mainId) => hasPathWithLinkerColumns(linkerColumnsMap, mainId, id))));
112
+ /** Add columns with fully compatible axes created from partial compatible ones */
113
+ export function enrichCompatible(blockAxes: Map<string, AxisId>, columns: PColumn<PColumnDataUniversal>[]) {
114
+ const result: PColumn<PColumnDataUniversal>[] = [];
115
+ columns.forEach((column) => {
116
+ result.push(...getAdditionalColumnsForColumn(blockAxes, column));
117
+ });
118
+ return result;
130
119
  }
131
120
 
132
- export const IS_VIRTUAL_COLUMN = 'pl7.app/graph/isVirtual'; // annotation for column duplicates with extended domains
133
- export const LABEL_ANNOTATION = 'pl7.app/label';
134
- export const LINKER_COLUMN_ANNOTATION = 'pl7.app/isLinkerColumn';
121
+ function getAdditionalColumnsForColumn(
122
+ blockAxes: Map<string, AxisId>,
123
+ column: PColumn<PColumnDataUniversal>,
124
+ ): PColumn<PColumnDataUniversal>[] {
125
+ const columnAxesIds = column.spec.axesSpec.map(getAxisId);
135
126
 
136
- /** Main column can have additional domains, if secondary column (meta-column) has all axes match main column axes
137
- we can add its copy with missed domain fields for compatibility */
138
- function getAdditionalColumnsForPair(
139
- mainColumn: PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor> | PColumnValues>,
140
- secondaryColumn: PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor> | PColumnValues>,
141
- linkerColumnsMap?: LinkerColumnsMap,
142
- ): PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor> | PColumnValues>[] {
143
- const mainAxesIds = mainColumn.spec.axesSpec.map(getAxisId);
144
- const secondaryAxesIds = secondaryColumn.spec.axesSpec.map(getAxisId);
145
-
146
- const isFullCompatible = checkFullCompatibility(mainColumn.spec, secondaryColumn.spec, linkerColumnsMap);
147
- if (isFullCompatible) { // in this case it isn't necessary to add more columns
148
- return [];
149
- }
150
- const isCompatible = checkCompatibility(mainColumn.spec, secondaryColumn.spec, linkerColumnsMap);
151
- if (!isCompatible) { // in this case it is impossible to add some compatible column
152
- return [];
127
+ if (columnAxesIds.every((id) => blockAxes.has(canonicalizeJson(id)))) {
128
+ return [column]; // the column is compatible with its own domains without modifications
153
129
  }
130
+
154
131
  // options with different possible domains for every axis of secondary column
155
- const secondaryIdsOptions = secondaryAxesIds.map((id) => {
156
- return mainAxesIds.filter((mainId) => matchAxisId(mainId, id));
132
+ const secondaryIdsOptions = columnAxesIds.map((id) => {
133
+ const result = [];
134
+ for (const [_, mainId] of blockAxes) {
135
+ if (matchAxisId(mainId, id) && !matchAxisId(id, mainId)) {
136
+ result.push(mainId);
137
+ }
138
+ }
139
+ return result;
157
140
  });
158
141
  // all possible combinations of axes with added domains
159
142
  const secondaryIdsVariants = getKeysCombinations(secondaryIdsOptions);
@@ -164,7 +147,7 @@ function getAdditionalColumnsForPair(
164
147
  const addedByVariantsDomainValues = secondaryIdsVariants.map((idsList) => {
165
148
  const addedSet = new Set<string>();
166
149
  idsList.map((axisId, idx) => {
167
- const d1 = secondaryColumn.spec.axesSpec[idx].domain;
150
+ const d1 = column.spec.axesSpec[idx].domain;
168
151
  const d2 = axisId.domain;
169
152
  Object.entries(d2 ?? {}).forEach(([key, value]) => {
170
153
  if (d1?.[key] === undefined) {
@@ -175,7 +158,7 @@ function getAdditionalColumnsForPair(
175
158
  });
176
159
  return ({
177
160
  ...axisId,
178
- annotations: secondaryColumn.spec.axesSpec[idx].annotations,
161
+ annotations: column.spec.axesSpec[idx].annotations,
179
162
  });
180
163
  });
181
164
  return addedSet;
@@ -186,10 +169,10 @@ function getAdditionalColumnsForPair(
186
169
  }
187
170
  });
188
171
 
189
- return secondaryIdsVariants.map((idsList, idx) => {
190
- const id = colId(secondaryColumn.id, idsList.map((id) => id.domain));
172
+ const additionalColumns = secondaryIdsVariants.map((idsList, idx) => {
173
+ const id = colId(column.id, idsList.map((id) => id.domain));
191
174
 
192
- const label = secondaryColumn.spec.annotations?.[LABEL_ANNOTATION] ?? '';
175
+ const label = column.spec.annotations?.[LABEL_ANNOTATION] ?? '';
193
176
  const labelDomainPart = ([...addedByVariantsDomainValues[idx]])
194
177
  .filter((str) => addedNotToAllVariantsDomainValues.has(str))
195
178
  .sort()
@@ -197,7 +180,7 @@ function getAdditionalColumnsForPair(
197
180
  .join(' / ');
198
181
 
199
182
  const annotations: Record<string, string> = {
200
- ...secondaryColumn.spec.annotations,
183
+ ...column.spec.annotations,
201
184
  [IS_VIRTUAL_COLUMN]: 'true',
202
185
  };
203
186
  if (label || labelDomainPart) {
@@ -207,101 +190,58 @@ function getAdditionalColumnsForPair(
207
190
  return {
208
191
  id: id as PObjectId,
209
192
  spec: {
210
- ...secondaryColumn.spec,
193
+ ...column.spec,
211
194
  axesSpec: idsList.map((axisId, idx) => ({
212
195
  ...axisId,
213
- annotations: secondaryColumn.spec.axesSpec[idx].annotations,
196
+ annotations: column.spec.axesSpec[idx].annotations,
214
197
  })),
215
198
  annotations,
216
199
  },
217
- data: secondaryColumn.data,
200
+ data: column.data,
218
201
  };
219
202
  });
220
- }
221
203
 
222
- export function getAdditionalColumns(
223
- columns: PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor> | PColumnValues>[],
224
- linkerColumnsMap: LinkerColumnsMap = new Map(),
225
- ): PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor> | PColumnValues>[] {
226
- const additionalColumns: PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor> | PColumnValues>[] = [];
227
- for (let i = 0; i < columns.length; i++) {
228
- for (let j = i + 1; j < columns.length; j++) {
229
- const column1 = columns[i];
230
- const column2 = columns[j];
231
-
232
- // check if column 1 is meta for column 2 or backward
233
- additionalColumns.push(
234
- ...getAdditionalColumnsForPair(column1, column2, linkerColumnsMap),
235
- ...getAdditionalColumnsForPair(column2, column1, linkerColumnsMap),
236
- );
237
- }
238
- }
239
- return additionalColumns;
240
- }
241
-
242
- export function enrichColumnsWithCompatible(
243
- mainColumns: PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor> | PColumnValues>[],
244
- secondaryColumns: PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor> | PColumnValues>[],
245
- linkerColumns: PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor> | PColumnValues>[] = [],
246
- linkerColumnsMap: LinkerColumnsMap = new Map(),
247
- ): PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor> | PColumnValues>[] {
248
- const mainColumnsIds = new Set<PObjectId>();
249
- const mainColumnsBySpec = new Map<CanonicalizedJson<PColumnSpecId>, typeof mainColumns[number]>();
250
- mainColumns.forEach((column) => {
251
- mainColumnsIds.add(column.id);
252
- mainColumnsBySpec.set(canonicalizeJson(getPColumnSpecId(column.spec)), column);
253
- });
254
-
255
- const secondaryColumnsBySpec = new Map<CanonicalizedJson<PColumnSpecId>, typeof secondaryColumns[number]>();
256
- for (const secondaryColumn of secondaryColumns) {
257
- if (mainColumnsIds.has(secondaryColumn.id)) continue;
258
-
259
- const spec = canonicalizeJson(getPColumnSpecId(secondaryColumn.spec));
260
- if (mainColumnsBySpec.has(spec)) continue;
261
-
262
- for (const mainColumn of mainColumnsBySpec.values()) {
263
- if (checkCompatibility(mainColumn.spec, secondaryColumn.spec, linkerColumnsMap)) {
264
- secondaryColumnsBySpec.set(spec, secondaryColumn);
265
- break;
266
- }
267
- }
268
- }
269
-
270
- for (const linkerColumn of linkerColumns) {
271
- if (mainColumnsIds.has(linkerColumn.id)) continue;
272
-
273
- const spec = canonicalizeJson(getPColumnSpecId(linkerColumn.spec));
274
- if (mainColumnsBySpec.has(spec)) continue;
275
- if (secondaryColumnsBySpec.has(spec)) continue;
276
-
277
- mainColumnsIds.add(linkerColumn.id);
278
- mainColumnsBySpec.set(spec, linkerColumn);
279
- }
280
-
281
- return [...mainColumnsBySpec.values(), ...secondaryColumnsBySpec.values()];
204
+ return [column, ...additionalColumns];
282
205
  }
283
206
 
284
207
  export function createPFrameForGraphs<A, U>(
285
208
  ctx: RenderCtx<A, U>,
286
- blockColumns: PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor> | PColumnValues>[] | undefined,
209
+ blockColumns: PColumn<PColumnDataUniversal>[] | undefined,
287
210
  ): PFrameHandle | undefined {
288
211
  if (!blockColumns) return undefined;
289
212
 
290
- const upstreamColumns = ctx.resultPool
291
- .getData()
292
- .entries.map((v) => v.obj)
293
- .filter(isPColumn);
213
+ const columns = new PColumnCollection();
214
+ columns.addColumnProvider(ctx.resultPool);
215
+ columns.addColumns(blockColumns);
216
+
217
+ // all possible axes from block columns
218
+ const allAxes = new Map<CanonicalizedJson<AxisId>, AxisId>();
219
+ for (const c of blockColumns) {
220
+ for (const id of c.spec.axesSpec) {
221
+ const aid = getAxisId(id);
222
+ allAxes.set(canonicalizeJson(aid), aid);
223
+ }
224
+ }
294
225
 
295
- const allAvailableColumns = [...blockColumns, ...upstreamColumns];
296
226
  // all linker columns always go to pFrame - even it's impossible to use some of them they all are hidden
297
- const linkerColumns = allAvailableColumns.filter((col) => isLinkerColumn(col.spec));
227
+ const linkerColumns = columns.getColumns((spec) => isLinkerColumn(spec)) ?? [];
228
+ const availableWithLinkersAxes = getAvailableWithLinkersAxes(linkerColumns, allAxes);
229
+ const labelColumns = columns.getColumns(isLabelColumn) ?? [];
298
230
 
299
- const linkerColumnsMap = getLinkerColumnsMap(linkerColumns);
300
- const columnsWithCompatibleFromUpstream = enrichColumnsWithCompatible(blockColumns, upstreamColumns, linkerColumns, linkerColumnsMap);
231
+ // all possible axes from connected linkers
232
+ for (const item of availableWithLinkersAxes) {
233
+ allAxes.set(...item);
234
+ }
235
+
236
+ let compatible = (columns.getColumns([...allAxes.values()]
237
+ .map((ax) => ({
238
+ axes: [ax],
239
+ partialAxesMatch: true,
240
+ }))) ?? []).filter((column) => !isLabelColumn(column.spec));
241
+ compatible = compatible.concat(labelColumns); // add all available labels columns to avoid problems with fixed axes, but don't add twice
301
242
 
302
- // additional columns are duplicates with extra fields in domains for compatibility in all possible pairs of columns set
303
- // const extendedColumns = [...columnsWithCompatibleFromUpstream, ...getAdditionalColumns(columnsWithCompatibleFromUpstream, linkerColumnsMap)];
304
- const extendedColumns = [...columnsWithCompatibleFromUpstream];
243
+ // additional columns are duplicates with extra fields in domains for compatibility if there are ones with partial match
244
+ const extendedColumns = enrichCompatible(allAxes, compatible);
305
245
 
306
246
  // if at least one column is not yet ready, we can't show the table
307
247
  if (
@@ -28,6 +28,7 @@ import {
28
28
  import type {
29
29
  AxisLabelProvider,
30
30
  ColumnProvider,
31
+ PColumnDataUniversal,
31
32
  RenderCtx,
32
33
  } from '../render';
33
34
  import {
@@ -363,7 +364,7 @@ export function isLabelColumn(column: PColumnSpec) {
363
364
  /** Get all label columns from the result pool */
364
365
  export function getAllLabelColumns(
365
366
  resultPool: AxisLabelProvider & ColumnProvider,
366
- ): PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor>>[] | undefined {
367
+ ): PColumn<PColumnDataUniversal>[] | undefined {
367
368
  return new PColumnCollection()
368
369
  .addAxisLabelProvider(resultPool)
369
370
  .addColumnProvider(resultPool)
@@ -376,8 +377,8 @@ export function getAllLabelColumns(
376
377
  /** Get label columns matching the provided columns from the result pool */
377
378
  export function getMatchingLabelColumns(
378
379
  columns: PColumnIdAndSpec[],
379
- allLabelColumns: PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor>>[],
380
- ): PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor>>[] {
380
+ allLabelColumns: PColumn<PColumnDataUniversal>[],
381
+ ): PColumn<PColumnDataUniversal>[] {
381
382
  // split input columns into label and value columns
382
383
  const inputLabelColumns: typeof columns = [];
383
384
  const inputValueColumns: typeof columns = [];
@@ -485,8 +486,8 @@ export function allColumnsComputed(
485
486
  }
486
487
 
487
488
  function createPTableDef(
488
- columns: PColumn<TreeNodeAccessor | PColumnValues | DataInfo<TreeNodeAccessor>>[],
489
- labelColumns: PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor>>[],
489
+ columns: PColumn<PColumnDataUniversal>[],
490
+ labelColumns: PColumn<PColumnDataUniversal>[],
490
491
  coreJoinType: 'inner' | 'full',
491
492
  filters: PTableRecordSingleValueFilterV2[],
492
493
  sorting: PTableSorting[],
package/src/render/api.ts CHANGED
@@ -51,6 +51,8 @@ import type { LabelDerivationOps } from './util/label';
51
51
  import { deriveLabels } from './util/label';
52
52
  import type { APColumnSelectorWithSplit } from './util/split_selectors';
53
53
 
54
+ export type PColumnDataUniversal = TreeNodeAccessor | DataInfo<TreeNodeAccessor> | PColumnValues;
55
+
54
56
  /**
55
57
  * Helper function to match domain objects
56
58
  * @param query Optional domain to match against
@@ -73,7 +75,7 @@ export type UniversalColumnOption = { label: string; value: SUniversalPColumnId
73
75
  * @param data Data from a PColumn to transform
74
76
  * @returns Transformed data compatible with platform API
75
77
  */
76
- function transformPColumnData(data: PColumn<TreeNodeAccessor | PColumnValues | DataInfo<TreeNodeAccessor>>):
78
+ function transformPColumnData(data: PColumn<PColumnDataUniversal>):
77
79
  PColumn<PColumnValues | AccessorHandle | DataInfo<AccessorHandle>> {
78
80
  return mapPObjectData(data, (d) => {
79
81
  if (d instanceof TreeNodeAccessor) {
@@ -191,7 +193,7 @@ export class ResultPool implements ColumnProvider, AxisLabelProvider {
191
193
  anchorsOrCtx: AnchoredIdDeriver | Record<string, PColumnSpec | PlRef>,
192
194
  predicateOrSelectors: ((spec: PColumnSpec) => boolean) | APColumnSelectorWithSplit | APColumnSelectorWithSplit[],
193
195
  opts?: UniversalPColumnOpts,
194
- ): PColumn<DataInfo<TreeNodeAccessor> | TreeNodeAccessor>[] | undefined {
196
+ ): PColumn<PColumnDataUniversal>[] | undefined {
195
197
  const anchorCtx = this.resolveAnchorCtx(anchorsOrCtx);
196
198
  if (!anchorCtx) return undefined;
197
199
  return new PColumnCollection()
@@ -565,7 +567,7 @@ export class RenderCtx<Args, UiState> {
565
567
  return this.resultPool.findLabels(axis);
566
568
  }
567
569
 
568
- private verifyInlineAndExplicitColumnsSupport(columns: PColumn<TreeNodeAccessor | PColumnValues | DataInfo<TreeNodeAccessor>>[]) {
570
+ private verifyInlineAndExplicitColumnsSupport(columns: PColumn<PColumnDataUniversal>[]) {
569
571
  const hasInlineColumns = columns.some((c) => !(c.data instanceof TreeNodeAccessor) || isDataInfo(c.data)); // Updated check for DataInfo
570
572
  const inlineColumnsSupport = this.ctx.featureFlags?.inlineColumnsSupport === true;
571
573
  if (hasInlineColumns && !inlineColumnsSupport) throw Error(`Inline or explicit columns not supported`); // Combined check
@@ -574,7 +576,7 @@ export class RenderCtx<Args, UiState> {
574
576
  }
575
577
 
576
578
  // TODO remove all non-PColumn fields
577
- public createPFrame(def: PFrameDef<TreeNodeAccessor | PColumnValues | DataInfo<TreeNodeAccessor>>): PFrameHandle {
579
+ public createPFrame(def: PFrameDef<PColumnDataUniversal>): PFrameHandle {
578
580
  this.verifyInlineAndExplicitColumnsSupport(def);
579
581
  return this.ctx.createPFrame(
580
582
  def.map((c) => transformPColumnData(c)),
@@ -582,24 +584,24 @@ export class RenderCtx<Args, UiState> {
582
584
  }
583
585
 
584
586
  // TODO remove all non-PColumn fields
585
- public createPTable(def: PTableDef<PColumn<TreeNodeAccessor | PColumnValues | DataInfo<TreeNodeAccessor>>>): PTableHandle;
587
+ public createPTable(def: PTableDef<PColumn<PColumnDataUniversal>>): PTableHandle;
586
588
  public createPTable(def: {
587
- columns: PColumn<TreeNodeAccessor | PColumnValues | DataInfo<TreeNodeAccessor>>[];
589
+ columns: PColumn<PColumnDataUniversal>[];
588
590
  filters?: PTableRecordFilter[];
589
591
  /** Table sorting */
590
592
  sorting?: PTableSorting[];
591
593
  }): PTableHandle;
592
594
  public createPTable(
593
595
  def:
594
- | PTableDef<PColumn<TreeNodeAccessor | PColumnValues | DataInfo<TreeNodeAccessor>>>
596
+ | PTableDef<PColumn<PColumnDataUniversal>>
595
597
  | {
596
- columns: PColumn<TreeNodeAccessor | PColumnValues | DataInfo<TreeNodeAccessor>>[];
598
+ columns: PColumn<PColumnDataUniversal>[];
597
599
  filters?: PTableRecordFilter[];
598
600
  /** Table sorting */
599
601
  sorting?: PTableSorting[];
600
602
  },
601
603
  ): PTableHandle {
602
- let rawDef: PTableDef<PColumn<TreeNodeAccessor | PColumnValues | DataInfo<TreeNodeAccessor>>>;
604
+ let rawDef: PTableDef<PColumn<PColumnDataUniversal>>;
603
605
  if ('columns' in def) {
604
606
  rawDef = {
605
607
  src: {
@@ -1,7 +1,6 @@
1
1
  import type {
2
2
  AnchoredIdDeriver,
3
3
  AxisId,
4
- DataInfo,
5
4
  PColumn,
6
5
  PColumnSelector,
7
6
  PColumnSpec,
@@ -13,6 +12,7 @@ import type {
13
12
  PartitionedDataInfoEntries,
14
13
  ResolveAnchorsOptions,
15
14
  NativePObjectId,
15
+ PColumnValues,
16
16
  } from '@milaboratories/pl-model-common';
17
17
  import {
18
18
  selectorsToPredicate,
@@ -32,9 +32,17 @@ import type { APColumnSelectorWithSplit, PColumnSelectorWithSplit } from './spli
32
32
  import canonicalize from 'canonicalize';
33
33
  import { getUniquePartitionKeys, convertOrParsePColumnData } from './pcolumn_data';
34
34
  import { filterDataInfoEntries } from './axis_filtering';
35
+ import type { PColumnDataUniversal } from '../api';
36
+
37
+ function isPColumnValues(value: unknown): value is PColumnValues {
38
+ if (!Array.isArray(value)) return false;
39
+ if (value.length === 0) return true;
40
+ const first = value[0];
41
+ return typeof first === 'object' && first !== null && 'key' in first && 'val' in first;
42
+ }
35
43
 
36
44
  export interface ColumnProvider {
37
- selectColumns(selectors: ((spec: PColumnSpec) => boolean) | PColumnSelector | PColumnSelector[]): PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor> | undefined>[];
45
+ selectColumns(selectors: ((spec: PColumnSpec) => boolean) | PColumnSelector | PColumnSelector[]): PColumn<PColumnDataUniversal | undefined>[];
38
46
  }
39
47
 
40
48
  export interface AxisLabelProvider {
@@ -45,13 +53,13 @@ export interface AxisLabelProvider {
45
53
  * A simple implementation of {@link ColumnProvider} backed by a pre-defined array of columns.
46
54
  */
47
55
  class ArrayColumnProvider implements ColumnProvider {
48
- constructor(private readonly columns: PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor> | undefined>[]) {}
56
+ constructor(private readonly columns: PColumn<PColumnDataUniversal | undefined>[]) {}
49
57
 
50
58
  selectColumns(selectors: ((spec: PColumnSpec) => boolean) | PColumnSelector | PColumnSelector[]):
51
- PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor> | undefined>[] {
59
+ PColumn<PColumnDataUniversal | undefined>[] {
52
60
  const predicate = typeof selectors === 'function' ? selectors : selectorsToPredicate(selectors);
53
61
  // Filter based on spec, ignoring data type for now
54
- return this.columns.filter((column): column is PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor> | undefined> => predicate(column.spec));
62
+ return this.columns.filter((column): column is PColumn<PColumnDataUniversal | undefined> => predicate(column.spec));
55
63
  }
56
64
  }
57
65
 
@@ -59,7 +67,7 @@ export type PColumnEntryWithLabel = {
59
67
  id: PObjectId;
60
68
  spec: PColumnSpec;
61
69
  /** Lazy calculates the data, returns undefined if data is not ready. */
62
- data(): DataInfo<TreeNodeAccessor> | TreeNodeAccessor | undefined;
70
+ data(): PColumnDataUniversal | undefined;
63
71
  label: string;
64
72
  };
65
73
 
@@ -79,7 +87,7 @@ type AxisFilterInfo = {
79
87
  // Intermediate representation for columns requiring splitting
80
88
  type IntermediateSplitEntry = {
81
89
  type: 'split';
82
- originalColumn: PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor> | undefined>;
90
+ originalColumn: PColumn<PColumnDataUniversal | undefined>;
83
91
  spec: PColumnSpec;
84
92
  /** With splitting axes removed */
85
93
  adjustedSpec: PColumnSpec;
@@ -90,7 +98,7 @@ type IntermediateSplitEntry = {
90
98
  // Intermediate representation for columns NOT requiring splitting
91
99
  type IntermediateDirectEntry = {
92
100
  type: 'direct';
93
- originalColumn: PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor> | undefined>;
101
+ originalColumn: PColumn<PColumnDataUniversal | undefined>;
94
102
  spec: PColumnSpec;
95
103
  /** The same as `spec` */
96
104
  adjustedSpec: PColumnSpec;
@@ -169,7 +177,7 @@ type UniversalPColumnOpts = UniversalPColumnOptsNoDeriver & {
169
177
  } & ResolveAnchorsOptions;
170
178
 
171
179
  export class PColumnCollection {
172
- private readonly defaultProviderStore: PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor> | undefined>[] = [];
180
+ private readonly defaultProviderStore: PColumn<PColumnDataUniversal | undefined>[] = [];
173
181
  private readonly providers: ColumnProvider[] = [new ArrayColumnProvider(this.defaultProviderStore)];
174
182
  private readonly axisLabelProviders: AxisLabelProvider[] = [];
175
183
 
@@ -185,12 +193,12 @@ export class PColumnCollection {
185
193
  return this;
186
194
  }
187
195
 
188
- public addColumns(columns: PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor> | undefined>[]): this {
196
+ public addColumns(columns: PColumn<PColumnDataUniversal | undefined>[]): this {
189
197
  this.defaultProviderStore.push(...columns);
190
198
  return this;
191
199
  }
192
200
 
193
- public addColumn(column: PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor> | undefined>): this {
201
+ public addColumn(column: PColumn<PColumnDataUniversal | undefined>): this {
194
202
  this.defaultProviderStore.push(column);
195
203
  return this;
196
204
  }
@@ -256,7 +264,7 @@ export class PColumnCollection {
256
264
  currentSelector = rawSelector as PColumnSelectorWithSplit | ((spec: PColumnSpec) => boolean);
257
265
 
258
266
  const selectedIds = new Set<PObjectId>();
259
- const selectedColumns: PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor> | undefined>[] = [];
267
+ const selectedColumns: PColumn<PColumnDataUniversal | undefined>[] = [];
260
268
  for (const provider of this.providers) {
261
269
  const providerColumns = provider.selectColumns(currentSelector);
262
270
  for (const col of providerColumns) {
@@ -283,6 +291,8 @@ export class PColumnCollection {
283
291
  const originalSpec = column.spec;
284
292
 
285
293
  if (needsSplitting) {
294
+ if (isPColumnValues(column.data))
295
+ throw new Error(`Splitting is not supported for PColumns with PColumnValues data format. Column id: ${column.id}`);
286
296
  const dataEntries = convertOrParsePColumnData(column.data);
287
297
 
288
298
  if (!dataEntries) {
@@ -417,20 +427,20 @@ export class PColumnCollection {
417
427
 
418
428
  public getColumns(
419
429
  predicateOrSelectors: ((spec: PColumnSpec) => boolean) | APColumnSelectorWithSplit | APColumnSelectorWithSplit[],
420
- opts: UniversalPColumnOpts): PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor>>[] | undefined;
430
+ opts: UniversalPColumnOpts): PColumn<PColumnDataUniversal>[] | undefined;
421
431
  public getColumns(
422
432
  predicateOrSelectors: ((spec: PColumnSpec) => boolean) | PColumnSelectorWithSplit | PColumnSelectorWithSplit[],
423
- opts?: UniversalPColumnOptsNoDeriver): PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor>>[] | undefined;
433
+ opts?: UniversalPColumnOptsNoDeriver): PColumn<PColumnDataUniversal>[] | undefined;
424
434
  public getColumns(
425
435
  predicateOrSelectors: ((spec: PColumnSpec) => boolean) | APColumnSelectorWithSplit | APColumnSelectorWithSplit[],
426
- opts?: Optional<UniversalPColumnOpts, 'anchorCtx'>): PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor>>[] | undefined {
436
+ opts?: Optional<UniversalPColumnOpts, 'anchorCtx'>): PColumn<PColumnDataUniversal>[] | undefined {
427
437
  const entries = this.getUniversalEntries(predicateOrSelectors, {
428
438
  overrideLabelAnnotation: true, // default for getColumns
429
439
  ...(opts ?? {}),
430
440
  } as UniversalPColumnOpts);
431
441
  if (!entries) return undefined;
432
442
 
433
- const columns: PColumn<TreeNodeAccessor | DataInfo<TreeNodeAccessor>>[] = [];
443
+ const columns: PColumn<PColumnDataUniversal>[] = [];
434
444
  for (const entry of entries) {
435
445
  const data = entry.data();
436
446
  if (!data) {