Skip to content
1 change: 1 addition & 0 deletions changes/unreleased/calc-parameter-pins.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- A tree rendering (`-render '#tree:…'`, `%viz`, the Jupyter kernel's default) drew the directed parameters of a calculation, action or constraint definition or usage — `in x`, `out y`, `return` — as `attribute` members with membership edges, and an anonymous `return` as a bare `attribute`. They are now parameter pins of their owner's node, direction first (`in x : Integer`), the anonymous result named `result` as the standard names it; a usage typed by a definition shows each parameter once, the definition's as inherited. The text form lists them under the node, DOT and D2 draw them as pins, Mermaid and PlantUML list them in a `parameters` compartment.
22 changes: 17 additions & 5 deletions docs/project/view-rendering-forms.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,18 @@ the usage's end — the glyphs of SysML v2 §8.2.3.6, the diamond read from the
`Feature::isComposite` (`semantics.Model.UsageIsComposite`: a `ref`, an attribute, a port outside a
port, an end, a directed or an enumeration usage is non-composite). The writers draw that edge in
place of the containment line they draw to every other child, so a usage hangs off its owner by its
membership glyph alone. A usage listed in a compartment row instead of drawn as a node (the
membership glyph alone. The directed parameters of a calculation, action or constraint definition
or usage — `in`, `out`, `inout` and `return` — are no member nodes of a tree: `treeParameter`
(`tree_parameters.go`) pins each on its owner's node as a `Port` with `Parameter` set and the
direction `pinDirection` reads, where §8.2.3.17, §8.2.3.19 and §8.2.3.20 set a calc's, action's and
constraint's parameters (`param-l*`, `param-r*`, `param-b*`). A `return` declaring no name is the
`result` pin, named by the `Function::result` it redefines (§7.19.2); a usage typed by a definition
pins each parameter once, its own redefining ones as declared and the definition's others as
inherited (`^y`). The text form lists the pins under their node direction first (`in x : Integer`,
the type under `typings`), DOT and D2 draw them as pins, and Mermaid's flowchart and PlantUML's
class diagram, which have no pin on a tree node, list them in a `parameters` compartment
(`labeller.parameterLines`). The pins are the tree's model structure and stay whatever
`parameter-symbols` says, as its members do. A usage listed in a compartment row instead of drawn as a node (the
compartment foldings, or a positioned tree's unplaced members) folds its membership and typing into
one edge from its owner to each drawn definition, labelled with the usage's name and multiplicity
at the definition's end — the property association a block definition diagram draws. Both
Expand Down Expand Up @@ -451,7 +462,7 @@ table records what each rendering feature writes:
| Feature | Mermaid syntax and behavior |
| --- | --- |
| Definitions, regions, package kinds | Square flowchart nodes, `n0["…"]`; cases use stadium nodes, actors and subjects use keyword-bearing rectangles, and objectives use note nodes. Other non-symbol leaves are rounded `n0("…")` nodes. Cameo follows the DOT skin's rounded rule. |
| Tree containment | Plain shaped nodes joined by `---`; tree nodes are never subgraphs and have no synthetic anchors. |
| Tree containment | Plain shaped nodes joined by `---`; tree nodes are never subgraphs and have no synthetic anchors. A calc's, action's or constraint's parameter pins are lines of the node's label under a `parameters` heading (`in x : Integer`). |
| Initial, final, junction, fork and join | `f-circ` for initial and junction nodes, `fr-circ` for final nodes, and `fork` for fork/join bars. Only fork/join bars use the `control` class. Named fork/join nodes are listed in a notice because the bar draws no label. |
| Decision, merge, choice and history | Diamonds; empty and synthesized decision names use a blank diamond. Shallow/deep history use `(("H"))` and `(("H*"))`. |
| State pseudostates and final transitions | Mermaid state stereotypes (`<<fork>>`, `<<join>>`, `<<choice>>`) and `[*]` for initial/final markers. A final in the same state body is implicit; cross-body final transitions retain the explicit final and receive a notice. |
Expand Down Expand Up @@ -1203,7 +1214,8 @@ pseudostate shape only when nothing else is attached.
Mermaid and DOT trees draw containment — a tree of edges, no nested containers — so the three
forms show the same picture and a tree has no blocks. The Pilot's `comp def`/`comp usage`
element kinds exist only in the PlantUML fork and are not emitted; every element is a standard
`class`.
`class`, a calc's, action's or constraint's parameter pins listed in its label under a
`parameters` heading, as the Mermaid tree lists them.
- **`interconnection`** — nested `rectangle` blocks: a node with children is `rectangle "…" as
n<i> <<kind>> {` … `}`, indented two spaces a level, a leaf a one-line `rectangle`. A port is a
nested rectangle inside its owner, not a `portin`/`portout`: the released jar's port grammar
Expand Down Expand Up @@ -1391,8 +1403,8 @@ attribute a rendering in the model chooses it by:

| Element | Attribute | What it draws | Optional by |
| --- | --- | --- | --- |
| `parameter-symbols` | `showParameterSymbols` | A case's `actor`, `subject` and `stakeholder` parameters and an action's parameter pins as squares on the node's boundary. Without them a connection that ended at a parameter ends at the node that declares it: one line per connection, one node per actor. A tree lists a case's parameters as members, which stay. | §7.26.5; §8.2.3 `distinguished-parameter`, `distinguished-parameter-link` |
| `port-symbols` | `showPortSymbols` | A part's ports, and a constraint's parameters, as pins on its boundary. Without them a connection that ended at a port ends at the part, a binding that ended at a parameter at the constraint; a port drawn as a nested member of a tree is model structure and stays. Default. | §7.26.5; §8.2.3 `port-node` |
| `parameter-symbols` | `showParameterSymbols` | A case's `actor`, `subject` and `stakeholder` parameters and an action's parameter pins as squares on the node's boundary. Without them a connection that ended at a parameter ends at the node that declares it: one line per connection, one node per actor. A tree lists a case's parameters as members and a calc's, action's or constraint's as pins of their owner, all of which stay. | §7.26.5; §8.2.3 `distinguished-parameter`, `distinguished-parameter-link` |
| `port-symbols` | `showPortSymbols` | A part's ports, and a constraint's parameters, as pins on its boundary. Without them a connection that ended at a port ends at the part, a binding that ended at a parameter at the constraint; a port drawn as a nested member of a tree is model structure and stays, as are a tree's parameter pins. Default. | §7.26.5; §8.2.3 `port-node` |
| `compartments` | `showCompartments` | The lists of nested features inside a node. Default. | §7.26.5; §8.2.3 `compartment` |
| `edge-labels` | `showEdgeLabels` | The names of connections, flows, bindings and allocations along their lines. Without them no label box is left. A sequence diagram's message lines keep their labels: the label is the message. | §8.2.3 `edge-label` |
| `keywords` | `showKeywords` | The `«keyword»` over each name (`«part def»`, `«part»`, `«enum def»`, …), as the notation's examples, the pilot and SysON draw every node. Default. | §8.2.3 `keyword-list` |
Expand Down
7 changes: 7 additions & 0 deletions docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -859,6 +859,13 @@ pins in the edge's label (`n0 -- n1 : bread = b`) and the pins in a `' not repre
draws the pins an edge ends at as nodes nested in their owner, and names the rest in a
`# not represented:` notice.

A tree rendering draws the directed parameters of a calculation, action or constraint definition or
usage — `in`, `out`, `inout` and `return` — as parameter pins of their owner's node rather than as
nested members, a `return` declaring no name as the `result` output: `text` lists them under the
node direction first (`in x : Integer` with `typings` shown), `dot` and `d2` draw them as pins, and
`mermaid` and `plantuml` list them in a `parameters` compartment of the node's label. A usage typed
by a definition shows each parameter once, the definition's as inherited (`^y`).

Every `subgraph` of a Mermaid flowchart opens on a `direction` statement restating the
flowchart's, because Mermaid lays out a subgraph that states none without regard to the
flowchart's; a tree draws containment as edges, not subgraphs, so it carries none.
Expand Down
11 changes: 7 additions & 4 deletions internal/ir/view/d2.go
Original file line number Diff line number Diff line change
Expand Up @@ -354,10 +354,10 @@ func (w *d2Writer) writeNode(node *Node, scope, indent string) {
}

// pins are the ports drawn inside a node: an interconnection's under the
// ports display, an action's the ones an edge ends at.
// ports display, a tree's parameter pins, an action's the ones an edge ends at.
func (w *d2Writer) pins(node *Node) []Port {
switch {
case w.ports.interconnection:
case w.ports.interconnection, w.ports.tree:
return w.ports.of(node)
case w.ports.action:
var pins []Port
Expand All @@ -373,9 +373,12 @@ func (w *d2Writer) pins(node *Node) []Port {

// pinLabel is a pin's label: on an interconnection the port's stereotype over
// `name : Type` under the full display and the name alone under the minimal,
// on an action the pin's name.
// on a tree the parameter's direction, name and type, on an action the pin's name.
func (w *d2Writer) pinLabel(port Port) string {
if !w.ports.interconnection {
switch {
case w.ports.tree:
return parameterPinLine(port)
case !w.ports.interconnection:
return port.displayName()
}
label := w.ports.pinLabel(port)
Expand Down
2 changes: 2 additions & 0 deletions internal/ir/view/dot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ func TestGoldenDOT(t *testing.T) {
{"glyphs-definition", "notation-glyphs.sysml", "NotationGlyphs::Diagrams::definitionGlyphs", KindDefinition, false},
{"glyphs-package", "notation-glyphs.sysml", "NotationGlyphs::Diagrams::packageGlyphs", KindPackage, false},
{"glyphs-tree", "notation-glyphs.sysml", "NotationGlyphs::Diagrams::treeGlyphs", KindTree, false},
{"calc-parameters", "calc-parameters.sysml", "ParameterCalcViews::calculations", KindTree, false},
{"calc-parameter-definitions", "calc-parameters.sysml", "ParameterCalcViews::definitions", KindTree, false},
{"glyphs-interconnection", "notation-glyphs.sysml", "NotationGlyphs::Diagrams::lineGlyphs", KindInterconnection, false},
{"glyphs-action", "notation-glyphs.sysml", "NotationGlyphs::Diagrams::fillGlyphs", KindAction, false},
{"glyphs-mixed", "notation-glyphs.sysml", "NotationGlyphs::Diagrams::mixedGlyphs", KindMixed, false},
Expand Down
11 changes: 9 additions & 2 deletions internal/ir/view/geometry.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,13 +254,20 @@ func (r *Renderer) memberDress(view, owner *symbols.Symbol, name string, node *N
node.Style = &Style{Fill: s.Fill, Line: s.Line, Text: s.Text, Font: s.Font, FontSize: s.FontSize, Bold: s.Bold, Italic: s.Italic}
}
}
r.memberNotesOf(view, owner, name, node.ID, out)
return node
}

// memberNotesOf adds to out the Notes about the member owner has under name in
// view, anchored to anchor: those naming it as owner's, and the declaring
// owner's — not one naming it as another inheriting owner's.
func (r *Renderer) memberNotesOf(view, owner *symbols.Symbol, name, anchor string, out *Rendering) {
for _, site := range r.model.MemberNotesOf(view, owner, name) {
r.noteLayoutProblems(site, owner, out)
if n := site.Note; n != nil {
out.Notes = append(out.Notes, Note{Text: n.Text, Anchor: node.ID, X: n.X, Y: n.Y, Width: n.Width, Height: n.Height, HasSize: n.HasSize, Origin: site.Origin()})
out.Notes = append(out.Notes, Note{Text: n.Text, Anchor: anchor, X: n.X, Y: n.Y, Width: n.Width, Height: n.Height, HasSize: n.HasSize, Origin: site.Origin()})
}
}
return node
}

// routeOf is the waypoints the edge declared as elem follows in view, nil when
Expand Down
19 changes: 19 additions & 0 deletions internal/ir/view/label.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ type labeller struct {
simple map[*Node]string // a node's distinguishing suffix, when names are drawn simple
skin dotSkin // the DOT skin labels are composed and measured for
shown Shown // the optional notation elements the labels carry
// pinLines lists a node's parameter pins as label lines, for a form that
// draws no pin on a tree node.
pinLines bool
}

// labelsOf finds the namespace the named roots share — the longest run of
Expand Down Expand Up @@ -349,6 +352,7 @@ func (l labeller) lines(node *Node) []string {
}
lines = append(lines, l.headLines(node)...)
lines = append(lines, l.details(node)...)
lines = append(lines, l.parameterLines(node)...)
return append(lines, l.compartmentLines(node)...)
}

Expand All @@ -361,9 +365,24 @@ func (l labeller) titleLines(node *Node) []string {
}
lines = append(lines, head...)
lines = append(lines, l.details(node)...)
lines = append(lines, l.parameterLines(node)...)
return append(lines, l.compartmentLines(node)...)
}

// parameterLines are a node's parameter pins as the notation's `parameters`
// compartment, `in x : Integer` a line under the heading, where pinLines asks
// for them; nothing for a node with no pin.
func (l labeller) parameterLines(node *Node) []string {
if !l.pinLines || len(node.Ports) == 0 {
return nil
}
lines := []string{"parameters"}
for _, port := range node.Ports {
lines = append(lines, parameterPinLine(port))
}
return lines
}

// cameoStateDetails splits a state's detail into Cameo's compartment lines, one
// per behaviour, and drops the `initial` marker the initial dot already draws.
// The detail is split as notation, so a name quoting a comma stays one; the
Expand Down
2 changes: 2 additions & 0 deletions internal/ir/view/mermaid.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func (r *Rendering) MermaidWith(options Options) string {
direction := options.Direction
var b strings.Builder
labels := labelsOf(r.Roots, false, nil, r.Shown)
labels.pinLines = r.Kind == KindTree
labels.skin = skinOf(options.Style)
ports := r.portView(options.Ports)
r.writeMermaidFrontmatter(&b, labels, options, ports)
Expand Down Expand Up @@ -1288,6 +1289,7 @@ func mermaidLabel(node *Node, labels labeller, lines []string, title bool) strin
markdown = append(markdown, "**"+line+"**")
}
markdown = append(markdown, labels.details(node)...)
markdown = append(markdown, labels.parameterLines(node)...)
markdown = append(markdown, labels.compartmentLines(node)...)
return `"` + "`" + strings.Join(markdown, "\n") + "`" + `"`
}
Expand Down
3 changes: 2 additions & 1 deletion internal/ir/view/narrow.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@ func (n *narrowing) narrow(node *Node) {
for _, port := range node.Ports {
pinShown := n.shown.Has(ElementPortSymbols)
if port.Parameter {
pinShown = n.shown.Has(ElementParameterSymbols)
// A tree's parameter pins are model structure, listed as its members are.
pinShown = n.shown.Has(ElementParameterSymbols) || n.kind == KindTree
}
if !pinShown {
n.redirect[port.ID] = node.ID
Expand Down
4 changes: 4 additions & 0 deletions internal/ir/view/plantuml.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func (r *Rendering) PlantUMLWith(options Options) (string, error) {
w := &plantumlWriter{kind: r.Kind, borders: r.Kind.paletteBorders(), fills: familyFills{palette: options.Palette, tree: r.Kind == KindTree},
labels: labelsOf(r.Roots, false, nil, r.Shown), ports: r.portView(options.Ports), links: options.Links,
writtenPorts: make(map[string]struct{}), nesting: r.nestingEdges()}
w.labels.pinLines = r.Kind == KindTree
for _, root := range r.Roots {
w.fills.collect(root)
}
Expand Down Expand Up @@ -707,6 +708,9 @@ func (w *plantumlWriter) plantumlLabel(node *Node) string {
for _, line := range w.labels.details(node) {
parts = append(parts, plantumlText(line))
}
for _, line := range w.labels.parameterLines(node) {
parts = append(parts, plantumlText(line))
}
for _, line := range w.labels.compartmentLines(node) {
parts = append(parts, plantumlText(line))
}
Expand Down
2 changes: 2 additions & 0 deletions internal/ir/view/plantuml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ var plantumlGoldenCases = []struct {
{"glyphs-definition", "notation-glyphs.sysml", "NotationGlyphs::Diagrams::definitionGlyphs", KindDefinition},
{"glyphs-package", "notation-glyphs.sysml", "NotationGlyphs::Diagrams::packageGlyphs", KindPackage},
{"glyphs-tree", "notation-glyphs.sysml", "NotationGlyphs::Diagrams::treeGlyphs", KindTree},
{"calc-parameters", "calc-parameters.sysml", "ParameterCalcViews::calculations", KindTree},
{"calc-parameter-definitions", "calc-parameters.sysml", "ParameterCalcViews::definitions", KindTree},
{"glyphs-interconnection", "notation-glyphs.sysml", "NotationGlyphs::Diagrams::lineGlyphs", KindInterconnection},
{"glyphs-action", "notation-glyphs.sysml", "NotationGlyphs::Diagrams::fillGlyphs", KindAction},
{"glyphs-mixed", "notation-glyphs.sysml", "NotationGlyphs::Diagrams::mixedGlyphs", KindMixed},
Expand Down
11 changes: 7 additions & 4 deletions internal/ir/view/ports.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,12 @@ type portView struct {
// whose pins text writes under their nodes.
interconnection bool
action bool
mixed bool
minimal bool
connected map[string]bool
// tree reports a tree rendering, whose pins are the parameters of its
// calculations, actions and constraints, written direction first.
tree bool
mixed bool
minimal bool
connected map[string]bool
}

// portView is the ports the rendering draws under display, the minimal
Expand All @@ -99,7 +102,7 @@ func (r *Rendering) portView(display Ports) portView { return r.portViewOver(dis
// no pin stands for a connector the form leaves out.
func (r *Rendering) portViewOver(display Ports, edges []Edge) portView {
v := portView{interconnection: r.Kind == KindInterconnection || r.Kind == KindMixed, action: r.Kind == KindAction,
mixed: r.Kind == KindMixed, minimal: display != PortsFull && r.Kind.SupportsPorts()}
tree: r.Kind == KindTree, mixed: r.Kind == KindMixed, minimal: display != PortsFull && r.Kind.SupportsPorts()}
if !v.minimal {
return v
}
Expand Down
2 changes: 2 additions & 0 deletions internal/ir/view/render_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ func TestGoldenRenderings(t *testing.T) {
{"glyphs-definition", "notation-glyphs.sysml", "NotationGlyphs::Diagrams::definitionGlyphs", KindDefinition},
{"glyphs-package", "notation-glyphs.sysml", "NotationGlyphs::Diagrams::packageGlyphs", KindPackage},
{"glyphs-tree", "notation-glyphs.sysml", "NotationGlyphs::Diagrams::treeGlyphs", KindTree},
{"calc-parameters", "calc-parameters.sysml", "ParameterCalcViews::calculations", KindTree},
{"calc-parameter-definitions", "calc-parameters.sysml", "ParameterCalcViews::definitions", KindTree},
{"glyphs-interconnection", "notation-glyphs.sysml", "NotationGlyphs::Diagrams::lineGlyphs", KindInterconnection},
{"glyphs-action", "notation-glyphs.sysml", "NotationGlyphs::Diagrams::fillGlyphs", KindAction},
{"glyphs-mixed", "notation-glyphs.sysml", "NotationGlyphs::Diagrams::mixedGlyphs", KindMixed},
Expand Down
29 changes: 29 additions & 0 deletions internal/ir/view/testdata/calc-parameter-definitions.d2.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# ParameterCalcViews::definitions — tree rendering (render asTreeDiagram)
classes: {
definition: { style: { fill: white; stroke: "#181818"; stroke-width: 1; font-color: black; font-size: 14 } }
pin: { style: { fill: white; stroke: "#181818"; stroke-width: 1; font-color: black; font-size: 10 } }
}
n0: "«calc def»\nAdd" {
class: definition
"n0.0": "in x" { class: pin }
"n0.1": "in y" { class: pin }
"n0.2": "out result" { class: pin }
}
n1: "«calc def»\nScale" {
class: definition
"n1.0": "in x" { class: pin }
"n1.1": "in factor" { class: pin }
"n1.2": "inout count" { class: pin }
"n1.3": "out doubled" { class: pin }
"n1.4": "out r" { class: pin }
}
n2: "«action def»\nHeat" {
class: definition
"n2.0": "in bread" { class: pin }
"n2.1": "out toast" { class: pin }
}
n3: "«constraint def»\nBetween" {
class: definition
"n3.0": "in low" { class: pin }
"n3.1": "in value" { class: pin }
}
Loading
Loading