Navigation chains
A cell often needs to show something that lives near the row element rather than on it: the subject of a requirement, the value of a named attribute, the definition typing a usage. Navigation chains are how a column reaches such related elements. This page covers the steps chains are built from, naming a chain so that several columns can reuse it, and fanning out, where a chain splits a row into sub-rows.
A column’s columnFeature holds a
navigation chain: a small expression that consists of navigation steps. Evaluated once
per row, it results in the element the column represents.
A chain starts from baseViewElement, the default feature a cell represents: the
row’s own element. To make a cell about something related to the row element, wrap
baseViewElement in navigation steps: calculations that take an element and navigate
to elements related to it, such as Owner(…) for the owning element or
NamedUsage(…, "mass") for a feature by name. The first argument of a step is what
it navigates from; the remaining arguments are parameters:
// the "torque" attribute of each row element, looked up by name
:>> columnFeature = NamedUsage(baseViewElement, "torque");
Navigation calculations are declarative: they are interpreted by the Modeler, not evaluated as KerML. Only three things may appear in a chain: navigation steps from the SysideViews library, references to navigations named at view level, and literal or enumeration arguments. A column with an invalid chain degrades to empty read-only cells and explains itself in a banner above the table; the rest of the view keeps working (see Troubleshooting).
Navigation steps
Each step answers two independent questions:
What does it navigate to? This is the step’s family: an element’s owner, its heritage, its usage features, and so on.
How do the targets it finds reach the table? This is the step’s kind.
Step kinds
There are three kinds. They differ in whether their targets can be navigated further, and in what they do to the row:
Kind |
What it does with the targets it finds |
|---|---|
|
Yields at most one target, so further steps can chain onto it. Renders one
cell, and the table keeps its rows. In a step family this kind is spelled
|
|
Renders every target together in one read-only cell. Terminal: a list of
elements cannot be navigated onwards, so a |
|
Splits the column cells into one sub-row per target, changing the shape of the table rather than one cell. Must be given a name at view level (an anchor), and further steps chain from that name. See Fanning out rows. |
Some navigations can only ever find one element: an element’s owner, a requirement’s subject, a feature looked up by name. Those are Singles outright and come as a single step with no variants.
A navigation that may find several targets instead comes as a family of three steps,
one per kind, with the kind appended to the family name. The Heritage family
provides HeritageList, HeritageFan, and HeritageFirst. All three navigate
identically and take the same parameters; only the handling of their targets
differs.
List takes all the elements of the navigation and lists them in a single cell.
Since it returns multiple elements to a single cell it cannot be used as an intermediate
step - a List navigation is always the last in the chain.
Fan takes the elements of the navigation and splits them up into subrows. This allows
further navigation from this step, though some constraints apply (more in Fanning out rows)
First is the Single member of such a family - it assumes the navigation step will
have one and only one output. This allows further navigation, however when navigation
results in multiple elements the cell becomes read-only with a warning note rather than
quietly picking one (the truncated outcome).
A navigation chain is composed of nested navigation steps and is read from the inside out.
The example chain below uses DefinitionsFirst to hop from each part usage to the definition
typing it, and NamedUsage navigates on from there to that definition’s torque:
// the "torque" attribute of the definition typing each row element
:>> columnFeature = NamedUsage(DefinitionsFirst(baseViewElement), "torque");
Below is the full list of available navigation steps, their variants and their descriptions:
Step catalogue
Step |
Variants |
Navigates to |
|---|---|---|
|
Single |
The element’s owner (parent namespace). Applicable to namespaces. |
|
Single |
The requirement’s subject. Applicable to requirements; on any other row the cell is not applicable. |
|
List, Fan, First |
The definitions explicitly typing the usage, one hop. Implicit standard-library typing does not count, so an untyped usage yields nothing. Applicable to usages. |
|
List, Fan, First |
The definitions this definition directly specialises, one hop. Applicable to definitions. |
|
List, Fan, First |
All supertypes, walked transitively breadth-first, explicitly written
specialisations only. An optional |
|
List, Fan, First |
The element’s usage features, own and inherited, optionally narrowed to a
|
|
Single |
The usage feature whose effective name (usually the declared name) matches
the |
|
List, Fan, First |
The requirement’s |
|
Single |
The element’s owned metadata tag ( |
|
Single |
The usage through which the tree reached this row. Only valid in a
hierarchical table view, and only
applied directly to |
Step parameters
Step parameters bind positionally after the navigation input and must be literals or enumeration references; expressions computing a parameter value are not supported. The steps that take parameters:
Parameter |
Steps |
What it represents |
|---|---|---|
|
|
The name of the usage feature to look up, as a non-empty string literal. Matched against effective names; short names never match. |
|
|
The name of the tag’s metadata definition, as a non-empty string literal.
Which of the definition’s names it matches is chosen by |
|
|
A |
|
|
A |
|
|
|
|
|
A |
|
|
An integer literal bounding the heritage walk; |
// item usages of the row element, subtypes (e.g. parts) included
:>> columnFeature = UsageList(baseViewElement, UsageKind::itemUsage, true);
Two edge cases worth knowing:
Exact
constraintUsagematches by metaclass, which inside a requirement also includes itsassume/requireconstraints. It does not matchassert constraint, which is its own metaclass (assertedConstraintUsage).Enumeration members are variants, not features, so no navigation returns them.
Reusing a navigation
A chain does not have to be written out inside the column that uses it. Declaring a named navigation at view level gives a chain a name that any column can start from:
view enginesTable : TVD::TableView {
expose DroneProject::Fleet::*;
engine = NamedUsage(baseViewElement, "engine", UsageKind::partUsage);
view 'Engine Type' :> columnViews {
:>> columnFeature = DefinitionsFirst(engine);
attribute :>> featureRepresentation :> ColRep::declaredName;
}
view 'Engine Power' :> columnViews {
:>> columnFeature = NamedUsage(engine, "power",
UsageKind::attributeUsage);
attribute :>> featureRepresentation :> ColRep::featureValue;
}
view 'Engine Documentation' :> columnViews {
:>> columnFeature = engine;
attribute :>> featureRepresentation :> ColRep::specificDocumentation;
}
}
All three columns are about the engine, but the step that finds it is written once.
A column referencing a named navigation can also chain further steps onto it:
Engine Type continues to the definition typing the engine, Engine Power to one
of its attributes, while Engine Documentation uses the navigation directly.
A named navigation holds any chain, so it is worth reaching for whenever several columns share a prefix: the shared part is named once, and correcting it later means editing one line rather than every column that repeated it. Referencing one is exactly equivalent to writing its chain out in place, so it changes nothing about the rendered table.
Two restrictions apply:
A named navigation must navigate, not merely rename something that already has a name.
alias = engine;orroot = baseViewElement;is rejected with a hint to reference the original directly.Named navigations may not form a cycle, directly or through one another.
Naming a navigation is required in exactly one case: a fan-out. There the name is more than a convenience, because it also identifies which columns share the split rows, as the next section describes.
Fanning out rows
Every step so far produces at most one cell per row. A Fan step is different: it
turns one row into several, one per target it finds. A table whose rows are parts can
show one row per port of each part, while still showing each part’s own values
alongside.
Because splitting cells affects the whole row rather than one column, a fan-out is the one navigation that cannot sit inline in a column binding. It must be given a name, and a named navigation holding a fan-out is called an anchor. Columns then point at that anchor.
Take a model with a pump whose two ports share a definition, where only inlet
sets the maxPressure it declares, and a gauge with no ports at all:
port def FluidPort {
attribute maxPressure;
}
part def Station {
part pump {
port inlet : FluidPort {
attribute :>> maxPressure = 5.0;
}
port outlet : FluidPort;
}
part gauge;
}
The view fans each part’s row out over its ports:
ports = UsageFan(baseViewElement, UsageKind::portUsage);
view Part :> columnViews {
attribute :>> featureRepresentation :> ColRep::declaredName;
}
view Port :> columnViews {
:>> columnFeature = ports;
attribute :>> featureRepresentation :> ColRep::declaredName;
}
view 'Max Pressure' :> columnViews {
:>> columnFeature = NamedUsage(ports, "maxPressure",
UsageKind::attributeUsage);
attribute :>> featureRepresentation :> ColRep::featureValue;
attribute :>> defaultValue = "(unset)";
}
Over that model, the view renders:
Part Port Max Pressure
─────────────────────────────────
pump inlet 5.0
outlet (unset)
gauge N/A N/A
Three things in that picture are worth naming, because together they are what fanning out means.
The Part cell spans its sub-rows. Part never references the anchor, so it
stays one cell per part, covering the run of rows its ports produced.
Port and Max Pressure do reference it, so they get one cell per port.
outlet shows a value that is missing, gauge never reached an element that could
be represented. outlet exists, so the chain reached it and only the attribute is absent:
that is the column’s defaultValue. gauge has no ports at all, so there is no port
for either fanned cell to be about: those are notApplicableValue. See
Cell outcomes.
gauge still has a row. A fan-out never removes rows. A parent whose fan-out
found nothing keeps exactly one row, so nothing silently disappears from the table.
Anchors
An anchor is a named navigation whose outermost call is a Fan step. Unlike an
ordinary one, which is pure convenience, an anchor is load-bearing: it is the identity
that decides which columns share the rows it produced. The rules that follow are all
consequences of rows being a property of the table as a whole:
A
Fancall must be the outermost call of an anchor, never nested inside another call. A column then references one anchor and applies only non-fanning steps to it.An anchor starts from
baseViewElementor from exactly one other anchor, so anchors form a chain, never a tree. Chaining is how a table fans out twice, for example one row per item of each port:ports = UsageFan(baseViewElement, UsageKind::portUsage); items = UsageFan(ports, UsageKind::itemUsage);
Two anchors fanning out in parallel is rejected. Rows would be every combination of two unrelated sets, which carries no meaning, so this is the one chain error that disables the whole table instead of degrading a single column.
Columns sharing an anchor are aligned by that anchor: on any given row, their cells describe the same target. Two columns fanning over the same feature through separate anchors are not aligned, which is why reusing one anchor is the supported way to put several columns on one fanned target.
Fan-out targets appear in declaration order, so row order is stable across reloads.
Column levels
A column’s level is the number of fan-out steps in its chain, and it alone decides
how fine-grained that column’s cells are: Part above is level 0, Port and
Max Pressure are level 1, and a column chained off items would be level 2.
Level is independent of where a column appears. Reordering columns, whether through
columnOrder or by dragging, changes no row and
no cell value. A List step also keeps a column at its current level, since it
renders all of its targets into a single cell rather than splitting rows.
Merged cells
A cell covering several sub-rows renders once at the start of its run and spans them. Merging is by identity, not by matching text: two ports that happen to share a name are never merged into one, and a repeated value on genuinely different targets stays repeated.
Editing a merged cell edits the one element behind it, so it is a single change rather
than one per spanned row. Fanned cells write to their own target: an edit in
Max Pressure lands on that row’s port.
Sorting, filtering, and export
Fanned rows change what these operations mean, in ways that keep the table’s structure intact:
Sorting is level-scoped. Sorting by a level-1 column reorders the sub-rows inside each parent’s run rather than across the whole table, so rows are never torn out from under the cell spanning them.
Filtering applies to the fanned rows. Sub-rows that do not match drop out, and a parent drops too once none of its sub-rows survive. This is distinct from a fan-out that found nothing, which keeps its row.
CSV export writes the flattened table: one line per row, with a spanning value repeated on every line it covered, the way a pivot-table export does.
In a hierarchical table a fanned row’s sub-rows are siblings at the same tree position, and the node’s children hang off the last of them. The tree column itself may never fan out.