Skip to main content

Using p-columns in the model

The Block Model, written in TypeScript, acts as the bridge between the workflow and the UI. It serves two primary functions. First, it defines the block's arguments (args) and generates choice options for the UI components (like dropdowns) that set these arguments. Second, it processes results from the workflow and the project's result pool to generate outputs for visualization. This creates a reactive loop: user selections in the UI update the args, which in turn can trigger recalculations of the model's outputs.

The result pool (ctx.resultPool)

The ctx.resultPool object, available in model lifecycle methods like .output(), is the main entry point for accessing data that is available in the current project context. Crucially, the result pool contains the exports from all upstream blocks in the analysis chain, as well as the results from previous runs of the current block. It does not contain results from downstream blocks.

The result pool API provides methods to:

  • Discover available p-columns and generate options for UI selectors.
  • Retrieve the full specification (PColumnSpec) of any available data object by its reference.
  • Resolve a reference (PlRef) to a full p-column object, including its data.
  • Perform "anchored queries" to find p-columns that are related to another p-column.

Finding p-columns and generating UI options

A common task is to populate a dropdown menu with a list of available datasets or p-columns that a user can select as input for the block. The ctx.resultPool.getOptions() method is designed for this purpose.

It takes a query object that specifies the criteria for the p-columns to find. The query can match against any part of a p-column's spec, including its name, axesSpec, domain, and annotations.

Example: Populating a dataset dropdown

// model/src/index.ts
// ...
.output('datasetOptions', (ctx) =>
ctx.resultPool.getOptions([
{
axes: [{ name: 'pl7.app/sampleId' }],
annotations: { 'pl7.app/isAnchor': 'true' }
}
])
)
// ...

The getOptions() method returns an array of { label: string, value: PlRef, ... } objects, perfect for a UI dropdown component. The label is automatically derived from the p-column's metadata.

Resolving an object by reference

If you have a PlRef to a specific object (e.g., from the block's arguments), you can retrieve its full specification or the entire p-column object.

  • ctx.resultPool.getPColumnSpecByRef(ref): Returns the PColumnSpec for the given PlRef. This is lightweight and useful when you only need metadata.
  • ctx.resultPool.getPColumnByRef(ref): Returns the full PColumn object for the given PlRef, including its data accessor.

Using anchored queries

An "anchored query" is a powerful way to find p-columns that are related to another p-column that the user has already selected (the "anchor"). The ctx.resultPool.getAnchoredPColumns() method is designed for this.

Example: Finding all columns in a dataset

Imagine the user has selected a dataset (represented by an anchor p-column PlRef stored in ctx.args.datasetRef). We now want to find all other p-columns that belong to this dataset to display them in a table.

// model/src/index.ts
// ...
.output('datasetColumns', (ctx) => {
if (!ctx.args.datasetRef) {
return undefined;
}

// Use getAnchoredPColumns to find all columns related to our selected anchor.
const columns = ctx.resultPool.getAnchoredPColumns(
// 1. Define the anchor context. 'main' is our local name for the anchor.
{ main: ctx.args.datasetRef },
// 2. Define the selector. This looks for any p-column that has exactly
// one axis that is identical to the first axis of the 'main' anchor.
{ axes: [{ anchor: 'main', idx: 0 }] }
);

return columns; // This will be an array of PColumn objects or undefined
})
// ...

This is a common pattern for gathering all related data columns from a dataset selected by the user. The returned array of PColumn objects can then be passed to a UI component.

Preparing p-frames for UI components

Workflow outputs are often entire p-frames. The model needs to resolve these and prepare them for visualization components like PlAgDataTableV2 or GraphMaker.

For tabular view (PlAgDataTableV2)

The createPlDataTableV2 utility from @platforma-sdk/model is used to prepare data for the table component. It takes the array of PColumn objects (like those returned by getAnchoredPColumns) and bundles them into the format expected by the table component.

// model/src/index.ts
// ...
.output('displayTable', (ctx) => {
const pFrameHandle = ctx.outputs?.resolve('tableData'); // from workflow outputs
if (!pFrameHandle) return undefined;

const pCols = pFrameHandle.getPColumns();
if (!pCols) return undefined;

return createPlDataTableV2(ctx, pCols, () => true, ctx.uiState?.tableState);
})
// ...

The table component uses the spec of each p-column to determine how to render headers, format numbers, and set column visibility and order.

For GraphMaker

Similarly, the @platforma-sdk/model package provides a createPFrameForGraphs utility to format p-frame data specifically for consumption by GraphMaker components.

// model/src/index.ts
// ...
.output('clustersPlotData', (ctx) => {
// Resolve the p-frame exported by the workflow
const pFrameHandle = ctx.outputs?.resolve('clustersPf');
if (!pFrameHandle) return undefined;

// Get the constituent p-columns
const pCols = pFrameHandle.getPColumns();
if (!pCols) return undefined;

// Format the p-columns for GraphMaker
return createPFrameForGraphs(ctx, pCols);
})
//...

The GraphMaker component in the UI can then be bound to app.model.outputs.clustersPlotData to visualize the p-frame. The component's appearance (title, template, etc.) is typically configured separately in the model's withUiState section.