Contents Menu Expand Light mode Dark mode Auto light/dark, in light mode Auto light/dark, in dark mode Skip to content
longeron 0.11.0
longeron 0.11.0
  • Getting started
  • Tutorials
    • 1. The model is data
    • 2. The model executes
    • 3. Views for review
    • 4. Trades: sizing the fleet
    • 5. Individuals: populations, not possibilities
    • 6. Requirements: score, hunt, prove
    • 7. Geometry and the mission
    • 8. The knowledge graph
    • 9. The grand tour: one dashboard, every seam
  • Guides
    • Command-line reference
    • Workspaces & caching
    • API server & client
    • Validation
    • Evidence-linked models
    • Grammar conformance
    • SysML v2 notation coverage
    • Choosing an analysis
    • Development
  • Reference
    • Package API & errors
    • Object model
    • Parsing & building
    • Workspaces & caching
    • Interpreter
    • Validation
    • Units
    • Model editing
    • Evidence
    • Standard library
    • Interchange
    • M0 interpretations
    • API server & client
    • Command line
    • Diagrams
    • View persistence
    • Rendering
    • Replay
    • Analysis
      • MDAO
      • Trade studies
      • SMT
      • Verify
      • Requirements scoreboard
      • Visualization
      • Geometry
      • Linked selection
      • Mission 3D tracks
      • Structure views
      • Dashboard
      • Grand tour dashboard
      • Surfaces
    • Widgets
      • Model explorer
      • RDF graph in 3D
      • Mission viewer
      • Replay widget
      • The time seam
      • 3D viewer
    • Notation gallery — every implemented SysML v2 glyph
  • Architecture
    • Conformance methodology (design)
    • Geometry as model content (design)
    • The lofting framework and the multisection wing (design)
    • M0 interpretations for longeron (design)
    • Object-valued analysis I/O in the OpenMDAO bridge (design)
    • Design: the tutorial notebooks, rebuilt as one curriculum
    • The OCL stance (design)
    • Longeron and OpenMBEE: integration paths (design)
    • Design: data provenance — evidence-linked models
    • Model-defined analysis surfaces (design)
    • The time seam: one clock across the views (design)
    • Units and quantities (design)
    • Model-driven requirement-violation hunting (design)
    • Saving diagrams as SysML v2 views (design)
  • Release notes
Back to top
View this page
Edit this page

Notation gallery — every implemented SysML v2 glyph¶

One section per implemented family of the SysML v2 graphical notation (spec 8.2.3 and the notation-table figures). Each section shows the spec’s ground-truth crop (when present on this machine), the exact SysML source, the live interactive diagram, and a compact assert cell that fails loudly if the glyph regresses. Page numbers are the spec’s printed page numbers.

How to review in JupyterLab:

  1. Compare each diagram against the spec crop above it.

  2. Click nodes and edges to check the SELECTED look too: selection is a color change only — hollow glyph bodies (triangles, diamonds, circles) stay white, filled glyphs (diamonds, arrowheads, balls, badges, pins) follow the selection color, and stroke widths never fatten.

  3. Pan/zoom in on the small endpoint glyphs (shaft adornments, pins, membership circles); the toolbar’s search box highlights elements by name without touching the selection.

  4. Tick the review checklist at the bottom as you go.

Layout runs in the browser (elkjs via the vendored ipyelk), so this notebook executes headlessly and the diagrams lay themselves out when a frontend attaches. Spec crops are looked up under the directory named by the SYSML_SPEC_PAGES environment variable and degrade gracefully when it is unset.

import os
from pathlib import Path

from ipyelk.elements import Port
from IPython.display import Image, display

import longeron
from longeron import diagrams

_spec_dir = os.environ.get("SYSML_SPEC_PAGES")
SPEC_PAGES = Path(_spec_dir) if _spec_dir else None


def spec_crop(*names: str, width: int = 460) -> None:
    """Show ground-truth crops from the spec PDF when available."""

    for name in names:
        p = SPEC_PAGES / name if SPEC_PAGES else None
        display(Image(filename=str(p), width=width)) if p and p.exists() else print(
            f"spec crop not available: {name}"
        )


def walk(root):
    """The ELK node and all its descendants."""

    yield root
    for child in root.children:
        yield from walk(child)


def node(widget, node_id):
    """The diagram node whose id (= qualified name) matches."""

    return next(n for n in walk(widget.source.value) if n.id == node_id)


def nodes(widget, css):
    """All diagram nodes whose cssClasses carry the fragment."""

    return [n for n in walk(widget.source.value) if css in (n.properties.cssClasses or "")]


def edges(widget, css):
    """All edges (any depth) whose cssClasses carry the fragment."""

    return [e for n in walk(widget.source.value) for e in n.edges if css in e.properties.cssClasses]


def edge(widget, css):
    """The first edge whose cssClasses carry the fragment."""

    return edges(widget, css)[0]

1. Specialization family (printed pp. 36–37)¶

The whole family draws a solid line into a closed hollow triangle at the general / definition end; the relationships are told apart only by the shaft adornment tight behind the head (never by keyword labels):

Textual

Adornment

subclassification :> (defs)

none

feature typing :

two dots straddling the shaft (a colon)

subsetting :> (usages)

none

redefinition :>>

one perpendicular bar tick

reference subsetting ::>

2×2 dots (a double colon)

spec_crop(
    "spec-p67-subclassification.png",
    "spec-p67-part-defined-by-part-definition.png",
    "spec-p68-subsetting.png",
    "spec-p68-redefinition.png",
)
spec crop not available: spec-p67-subclassification.png
spec crop not available: spec-p67-part-defined-by-part-definition.png
spec crop not available: spec-p68-subsetting.png
spec crop not available: spec-p68-redefinition.png
m1a = longeron.loads("""
package Specializations {
    part def Machine;
    part def Vehicle :> Machine;    // subclassification: plain hollow triangle

    part car : Vehicle;             // feature typing: colon dots

    part vehicle;
    part truck :> vehicle;          // subsetting: plain hollow triangle

    part pool;
    part spare ::> pool;            // reference subsetting: 2x2 colon dots

    part def V { part engine; }
    part tuned : V { part engine :>> engine; }   // redefinition: bar tick
}
""")
w1a = diagrams.structure_diagram(m1a)
w1a
Diagram (static snapshot of the interactive widget)

Static snapshot -- run the notebook in JupyterLab (pixi run lab) to interact.

forms = {
    kind: edge(w1a, f"sysml-edge-{kind}").properties.shape.end
    for kind in ("specializes", "typed", "subsets", "redefines", "references")
}
assert forms == {
    "specializes": "generalization",
    "typed": "generalization-colon",
    "subsets": "generalization",
    "redefines": "generalization-tick",
    "references": "generalization-dcolon",
}, forms

1b. Satisfy longhand — «satisfy requirement» box + reference subsetting (printed p. 133)¶

A named satisfy usage draws as the «satisfy requirement» box, joined to the satisfied requirement by a reference-subsetting edge (the 2×2 colon dots behind the hollow triangle).

spec_crop("spec-p164-satisfy-requirement-longhand-notation-with-explicit-referenc.png")
spec crop not available: spec-p164-satisfy-requirement-longhand-notation-with-explicit-referenc.png
m1b = longeron.loads("""
package Requirements { requirement requirement1; }
package System {
    part sys {
        satisfy requirement satisfy1 references Requirements::requirement1;
    }
}
""")
w1b = diagrams.structure_diagram(m1b)
w1b
Diagram (static snapshot of the interactive widget)

Static snapshot -- run the notebook in JupyterLab (pixi run lab) to interact.

assert node(w1b, "System::sys::satisfy1").labels[0].text == "«satisfy requirement»"
ref = edge(w1b, "sysml-edge-references")
assert ref.properties.shape.end == "generalization-dcolon"
assert (ref.source.id, ref.target.id) == ("System::sys::satisfy1", "Requirements::requirement1")

2. Membership (printed pp. 26, 38)¶

2a. Feature membership — composite and referential diamonds (printed p. 38)¶

Definition-level membership edges (composition="defs", the default): a filled diamond at the whole end for composite members, a hollow diamond for referential (ref) members — role name on the line, the member’s multiplicity at the part end.

spec_crop(
    "spec-p69-feature-membership-iscomposite-true.png",
    "spec-p69-feature-membership-iscomposite-false.png",
)
spec crop not available: spec-p69-feature-membership-iscomposite-true.png
spec crop not available: spec-p69-feature-membership-iscomposite-false.png
m2a = longeron.loads("""
package Memberships {
    part def Wheel;
    part def Driver;
    part def Car {
        part wheels : Wheel [4];     // composite: filled diamond, [4] at the part end
        ref part driver : Driver;    // referential: hollow diamond
    }
}
""")
w2a = diagrams.structure_diagram(m2a, composition="defs")
w2a
Diagram (static snapshot of the interactive widget)

Static snapshot -- run the notebook in JupyterLab (pixi run lab) to interact.

comp = edge(w2a, "sysml-edge-member")
assert comp.properties.shape.start == "composition"
assert {"wheels", "[4]"} <= {label.text for label in comp.labels}
assert edge(w2a, "sysml-edge-refmember").properties.shape.start == "aggregation"

2b. Owned membership — the circle-plus presentation (printed p. 26)¶

membership="edges" swaps package nesting for the spec’s ALTERNATIVE owned-membership presentation: members become sibling nodes joined to their owning namespace by a solid edge with a true circle-plus (cross strokes spanning the full diameter) at the owning end.

spec_crop("spec-p57-membership-owned-member.png")
spec crop not available: spec-p57-membership-owned-member.png
m2b = longeron.loads("package Package0 { package Package1 { part def X; } }")
w2b = diagrams.structure_diagram(m2b, membership="edges")
w2b
Diagram (static snapshot of the interactive widget)

Static snapshot -- run the notebook in JupyterLab (pixi run lab) to interact.

owned = edges(w2b, "sysml-edge-owned")
assert {(e.source.id, e.target.id) for e in owned} == {
    ("Package0", "Package0::Package1"),
    ("Package0::Package1", "Package0::Package1::X"),
}
assert all(e.properties.shape.start == "owned-circle-plus" for e in owned)

2c. Unowned membership (alias) — hollow circle (printed p. 26)¶

An alias draws a solid line with a small hollow circle at the referencing namespace end and the alias name as the edge label.

spec_crop("spec-p57-membership-unowned-member-with-alias-name.png")
spec crop not available: spec-p57-membership-unowned-member-with-alias-name.png
m2c = longeron.loads("""
package Lib { part def Target; }
package App { alias T for Lib::Target; }
""")
w2c = diagrams.structure_diagram(m2c)
w2c
Diagram (static snapshot of the interactive widget)

Static snapshot -- run the notebook in JupyterLab (pixi run lab) to interact.

al = edge(w2c, "sysml-edge-alias")
assert al.properties.shape.start == "alias-circle" and al.labels[0].text == "T"
assert (al.source.id, al.target.id) == ("App", "Lib::Target")

3. Connectors (printed pp. 19, 66–67, 79, 132)¶

3a. Plain connect — no endpoint glyphs, cross multiplicities (printed p. 66)¶

A binary connection is a plain solid line; cross multiplicities render near the ends they constrain.

spec_crop("spec-p97-connection.png")
spec crop not available: spec-p97-connection.png
m3a = longeron.loads("""
package Connections {
    part def S {
        part a;
        part b;
        connect [1] a to [0..2] b;
    }
}
""")
w3a = diagrams.structure_diagram(m3a)
w3a
Diagram (static snapshot of the interactive widget)

Static snapshot -- run the notebook in JupyterLab (pixi run lab) to interact.

conn = edge(w3a, "sysml-edge-connect")
assert conn.properties.shape is None  # no endpoint glyphs on a plain connect
placements = {lb.text: lb.layoutOptions["elk.edgeLabels.placement"] for lb in conn.labels}
assert placements == {"[1]": "TAIL", "[0..2]": "HEAD"}

3b. Connection with direction indication (printed p. 66)¶

When the connection’s definition declares directed ends (sourceEnd / targetEnd), the connector line grows an open-V head at the target end and carries the name : Type label.

spec_crop(
    "spec-p96-connection-definition-with-direction-indication.png",
    "spec-p97-connection-with-direction-indication.png",
)
spec crop not available: spec-p96-connection-definition-with-direction-indication.png
spec crop not available: spec-p97-connection-with-direction-indication.png
m3b = longeron.loads("""
package Directed {
    part def Part1;
    part def Part2;
    connection def ConnectionDef2 {
        end [1..1] part sourceEnd : Part1;
        end [1..*] part targetEnd : Part2;
    }
    part part1 : Part1;
    part part2 : Part2;
    connection connection2 : ConnectionDef2 connect part1 to part2;
}
""")
w3b = diagrams.structure_diagram(m3b)
w3b
Diagram (static snapshot of the interactive widget)

Static snapshot -- run the notebook in JupyterLab (pixi run lab) to interact.

d = edge(w3b, "sysml-edge-directed")
assert d.properties.shape.end == "arrow"  # open-V head at the target end
assert d.labels[0].text == "connection2 : ConnectionDef2"

3c. N-ary connection — junction dot (printed p. 66)¶

connect (a, b, c) with three or more ends meets at a small filled junction dot, the connection’s name : Type label beside it.

spec_crop("spec-p97-connection-n-ary-with-3-ends.png")
spec crop not available: spec-p97-connection-n-ary-with-3-ends.png
m3c = longeron.loads("""
package Nary {
    part def ConnectionDef1;
    part part1;
    part part2;
    part part3;
    connection connection1 : ConnectionDef1 connect (part1, part2, part3);
}
""")
w3c = diagrams.structure_diagram(m3c)
w3c
Diagram (static snapshot of the interactive widget)

Static snapshot -- run the notebook in JupyterLab (pixi run lab) to interact.

j = nodes(w3c, "sysml-connjunction")[0]
assert j.labels[0].text == "connection1 : ConnectionDef1"
# spokes anchor on the dot's invisible center ports (in/out), so every
# line radiates from the junction itself -- never scattered boundary points
spokes = [
    e
    for e in edges(w3c, "sysml-edge-connect")
    if any(e.source is p or e.target is p for p in j.ports)
]
assert len(spokes) == 3

3d. Proxy connection — dot on the shallowest drawn ancestor (printed p. 67)¶

A connector end naming an undrawn nested feature (connect part2.part4 to ...) draws the spec’s small filled proxy dot on the border of the shallowest drawn ancestor, labeled with the residual path (.part4) inside the box, adjacent to the dot — exactly where the spec figure writes it — never an edge into the definition’s member box.

spec_crop("spec-p98-proxy-connection.png")
spec crop not available: spec-p98-proxy-connection.png
m3d = longeron.loads("""
package Proxies {
    part def Part2 { part part4; }
    part def Part3 { part part5; }
    part part1 {
        part part2 : Part2;
        part part3 : Part3;
        connect part2.part4 to part3.part5;
    }
}
""")
w3d = diagrams.structure_diagram(m3d)
w3d
Diagram (static snapshot of the interactive widget)

Static snapshot -- run the notebook in JupyterLab (pixi run lab) to interact.

p2 = node(w3d, "Proxies::part1::part2")
proxy = next(p for p in p2.ports if "sysml-port-proxy" in p.properties.cssClasses)
assert proxy.labels[0].text == ".part4" and proxy.properties.shape.use == "port-proxy"
# the residual-path label reads INSIDE the part box (spec p. 67 figure)
assert p2.layoutOptions["elk.portLabels.placement"] == "INSIDE"
assert "PORT_LABELS" in p2.layoutOptions["nodeSize.constraints"]

3e. Binding connector — the = glyph (printed p. 67)¶

A binding connector is a plain solid line with an = riding mid-span, no endpoint glyphs.

Reading the spec crop below: the = on the bottom edge is the binding; the diamonds belong to the surrounding figure (composite membership of the parts), not to the binding notation.

spec_crop("spec-p98-binding-connection.png")
spec crop not available: spec-p98-binding-connection.png
m3e = longeron.loads("""
package Bindings {
    part original;
    part alias_part;
    binding bind original = alias_part;
}
""")
w3e = diagrams.structure_diagram(m3e)
w3e
Diagram (static snapshot of the interactive widget)

Static snapshot -- run the notebook in JupyterLab (pixi run lab) to interact.

b = edge(w3e, "sysml-edge-binding")
assert [label.text for label in b.labels] == ["="] and b.properties.shape is None

3f. Dependency — binary and n-ary (printed p. 19)¶

Dependencies draw dashed open-V edges client → supplier with the optional (name) label; an n-ary dependency radiates dashed links from a small filled junction dot (client links plain, supplier links arrowed).

spec_crop("spec-p50-dependency.png", "spec-p50-dependency-nary.png")
spec crop not available: spec-p50-dependency.png
spec crop not available: spec-p50-dependency-nary.png
m3f = longeron.loads("""
package Dependencies {
    part a;
    part b;
    part c;
    part s;
    dependency Uses from a to s;
    dependency Multi from b, c to s;
}
""")
w3f = diagrams.structure_diagram(m3f)
w3f
Diagram (static snapshot of the interactive widget)

Static snapshot -- run the notebook in JupyterLab (pixi run lab) to interact.

deps = edges(w3f, "sysml-edge-dependency")
assert len(deps) == 2 and all(e.properties.shape.end == "arrow" for e in deps)
assert nodes(w3f, "sysml-junction")[0].labels[0].text == "(Multi)"
assert len(edges(w3f, "sysml-edge-depclient")) == 2  # unarrowed client links

3g. Satisfy shorthand — «satisfy» keyword edge (printed p. 132)¶

The anonymous shorthand satisfy R by sys; draws a solid line with an open-V arrow and the «satisfy» keyword from the satisfying element to the requirement.

spec_crop("spec-p163-satisfy-requirement-shorthand-notation.png")
spec crop not available: spec-p163-satisfy-requirement-shorthand-notation.png
m3g = longeron.loads("""
package Satisfies {
    requirement requirement1;
    part sys;
    satisfy requirement1 by sys;
}
""")
w3g = diagrams.structure_diagram(m3g)
w3g
Diagram (static snapshot of the interactive widget)

Static snapshot -- run the notebook in JupyterLab (pixi run lab) to interact.

sat = edge(w3g, "sysml-edge-satisfies")
assert sat.labels[0].text == "«satisfy»" and sat.properties.shape.end == "arrow"
assert (sat.source.id, sat.target.id) == ("Satisfies::sys", "Satisfies::requirement1")

3h. Allocation — «allocate» keyword edge + «allocation» box (printed p. 79)¶

Anonymous allocate a to b draws the «allocate» keyword arrow; a named allocation usage draws the «allocation» box form instead.

spec_crop("spec-p110-allocation.png", "spec-p110-allocation-definition.png")
spec crop not available: spec-p110-allocation.png
spec crop not available: spec-p110-allocation-definition.png
m3h = longeron.loads("""
package Allocations {
    part part1;
    part part2;
    allocate part1 to part2;                 // keyword edge

    allocation def AllocationDef1;
    part part3;
    part part4;
    allocation allocation1 : AllocationDef1  // box form
        allocate part3 to part4;
}
""")
w3h = diagrams.structure_diagram(m3h)
w3h
Diagram (static snapshot of the interactive widget)

Static snapshot -- run the notebook in JupyterLab (pixi run lab) to interact.

alc = edge(w3h, "sysml-edge-allocate")
assert alc.labels[0].text == "«allocate»" and alc.properties.shape.end == "arrow"
assert node(w3h, "Allocations::allocation1").labels[0].text == "«allocation»"

4. Flow connections (printed pp. 77, 81)¶

4a. Pin form — undrawn ports (printed p. 81)¶

A flow between features that are not drawn as port squares runs pin-to-pin: a small square source-output pin, a small square target-input pin with a filled arrowhead tight against it, and the payload item labeled near each end.

spec_crop("spec-p112-flow.png")
spec crop not available: spec-p112-flow.png
m4a = longeron.loads("""
package Flows {
    item def Item1;
    action def A { in x : Item1; out y : Item1; }
    action action1 : A;
    action action2 : A;
    flow of Item1 from action1.y to action2.x;
}
""")
w4a = diagrams.structure_diagram(m4a)
w4a
Diagram (static snapshot of the interactive widget)

Static snapshot -- run the notebook in JupyterLab (pixi run lab) to interact.

flow = edge(w4a, "sysml-edge-flow")
assert (flow.properties.shape.start, flow.properties.shape.end) == (
    "flow-source-pin",
    "flow-target-pin",
)
ends = [lb.layoutOptions["elk.edgeLabels.placement"] for lb in flow.labels if lb.text == "Item1"]
assert sorted(ends) == ["HEAD", "TAIL"]  # payload labeled near both ends

4b. Port-attached form — filled head only (printed p. 77)¶

When the flow’s ends resolve to drawn boundary port squares, the port already is the pin: the line runs square-to-square — straight between the facing borders — and keeps only the small filled arrowhead at the target port. The payload label (item1 : Item1, Table 14) rides the line near each end; port labels live inside the part bodies, like the spec’s figure. The receiving side uses the spec’s own conjugation pattern (pc : ~Pout — p. 77 receives on the ~ end), so its square shows the in arrow the flow enters through.

spec_crop("spec-p108-interface-as-node-with-flow.png")
spec crop not available: spec-p108-interface-as-node-with-flow.png
m4b = longeron.loads("""
package PortFlows {
    item def Item1;
    port def Pout { out item y : Item1; }
    part part0 {
        part part1 { port po : Pout; }
        part part2 { port pc : ~Pout; }
        flow of item1 : Item1 from part1.po to part2.pc;
    }
}
""")
w4b = diagrams.structure_diagram(m4b)
w4b
Diagram (static snapshot of the interactive widget)

Static snapshot -- run the notebook in JupyterLab (pixi run lab) to interact.

pflow = edge(w4b, "sysml-edge-portflow")
assert pflow.properties.shape.end == "flow-arrow" and pflow.properties.shape.start is None
assert isinstance(pflow.source, Port) and isinstance(pflow.target, Port)  # square-to-square
# the payload item label rides the line near BOTH ends (Table 14 flow row)
assert [label.text for label in pflow.labels] == ["item1 : Item1", "item1 : Item1"]
# the receiving square shows the flow entering: in-arrow on its west side
pc = next(p for p in node(w4b, "PortFlows::part0::part2").ports if p.id.endswith("::pc"))
assert pc.properties.shape.use == "port-in-west"
# and every port label sits INSIDE its part body (spec p. 108 figure)
for name in ("part1", "part2"):
    owner = node(w4b, f"PortFlows::part0::{name}")
    assert owner.layoutOptions["elk.portLabels.placement"] == "INSIDE"

5. Ports (printed pp. 59, 62, 75–76)¶

Port usages render as the spec’s small squares on the owning box’s border with the name : Type label inside the box, next to the square (where the spec’s part figures write it). The direction arrow inside the square derives from the port definition’s directed features (all in, all out, mixed → double-headed); a plain square draws no arrow. The arrow orients relative to the node interior — an in arrow points into the box from whatever border the square rides. Conjugation is textual (~Pin) and flips the arrow both ways (spec 7.12.3): pc : ~Pout receives, pd : ~Pin sends. Interfaces attach square-to-square.

spec_crop("spec-p90-part-with-ports.png", "spec-p93-port.png", "spec-p106-interface.png")
spec crop not available: spec-p90-part-with-ports.png
spec crop not available: spec-p93-port.png
spec crop not available: spec-p106-interface.png
m5 = longeron.loads("""
package Ports {
    item def Item1;
    port def Pin { in item x : Item1; }
    port def Pout { out item y : Item1; }
    port def Pio { in item a : Item1; out item b : Item1; }
    port def Plain;
    part part0 {
        part part1 { port po : Pout; port p4 : Plain; }
        part part2 { port pi : Pin; port pc : ~Pout; port pd : ~Pin; port pio : Pio; }
        interface if1 connect part1.po to part2.pc;
    }
}
""")
w5 = diagrams.structure_diagram(m5)
w5
Diagram (static snapshot of the interactive widget)

Static snapshot -- run the notebook in JupyterLab (pixi run lab) to interact.

squares = {p.id.split("::")[-1]: p for n in walk(w5.source.value) for p in n.ports if p.id}
arrows = {
    name: p.properties.shape.use if p.properties.shape else None for name, p in squares.items()
}
assert arrows == {
    "po": "port-out-east",  # out arrows LEAVE the box through its east border
    "p4": None,
    "pi": "port-in-west",  # in arrows ENTER the box through its west border
    "pc": "port-in-west",  # conjugation textual, arrow flipped out -> in
    "pd": "port-out-east",  # ... and in -> out
    "pio": "port-inout-east",
}
assert squares["pc"].labels[0].text == "pc : ~Pout"
assert squares["pd"].labels[0].text == "pd : ~Pin"
iface = edge(w5, "sysml-edge-connect")
assert iface.source is squares["po"] and iface.target is squares["pc"]  # square-to-square
assert iface.labels[0].text == "if1"

6. Behavior views (printed pp. 90–92, 97, 115–116, 227)¶

6a. Action flow — control glyphs, badges, dashed successions (printed pp. 92, 97, 227)¶

The succession control-flow graph the interpreter executes: start filled dot, done bullseye, fork/join thick filled bars, decision/merge empty rhombi, accept/send standard rounded action boxes with a filled top-left badge. Successions render dashed; guarded branches carry their [guard] label. The decision below has three ways out and the merge three ways in — each fan converges on a single anchor point of the glyph (fork/join bars deliberately distribute along the bar instead).

spec_crop(
    "spec-p123-actions-with-control-nodes.png",
    "spec-p128-accept-action.png",
    "spec-p128-send-action.png",
)
spec crop not available: spec-p123-actions-with-control-nodes.png
spec crop not available: spec-p128-accept-action.png
spec crop not available: spec-p128-send-action.png
m6a = longeron.loads("""
package Behaviors {
    item def Go;
    action def Flow {
        action prep { assign x := 1; }
        fork f;
        action a;
        action b;
        join j;
        decide d;
        merge g;
        action rx accept go : Go;
        action tx send new Go() via ch;
        action c;

        first start then prep;
        first prep then f;
        first f then a;
        first f then b;
        first a then j;
        first b then j;
        first j then d;
        first d if x > 0 then rx;
        first d if x < 0 then c;
        first d then g;
        first rx then tx;
        first tx then g;
        first c then g;
        first g then done;
    }
}
""")
w6a = diagrams.action_diagram(m6a.find("Behaviors::Flow"))
w6a
Diagram (static snapshot of the interactive widget)

Static snapshot -- run the notebook in JupyterLab (pixi run lab) to interact.

by_id = {n.id: n for n in walk(w6a.source.value) if n.id}
fork = by_id["Behaviors::Flow::f"]
assert (fork.width, fork.height) == (6, 40) and "sysml-ctrl-bar" in fork.properties.cssClasses
decision = by_id["Behaviors::Flow::d"]
assert type(decision.properties.shape).__name__ == "Diamond"
assert [p.layoutOptions["elk.port.side"] for p in decision.ports] == ["WEST", "EAST"]  # anchors
assert len(edges(w6a, "sysml-edge-guarded")) == 2  # [x > 0] and [x < 0]
assert by_id["Behaviors::Flow::rx"].labels[0].properties.shape.use == "accept-badge"
assert by_id["Behaviors::Flow::tx"].labels[0].properties.shape.use == "send-badge"
assert "glyph-core" in nodes(w6a, "sysml-final")[0].properties.shape.use  # done bullseye
assert nodes(w6a, "sysml-marker")  # start dot

6b. Terminate — circle-X (printed p. 227)¶

A terminate; statement draws the spec’s circle with an inscribed X.

spec_crop("spec-p258.png", width=700)  # the behavior-glyph BNF page (start/done/terminate/...)
spec crop not available: spec-p258.png
m6b = longeron.loads("""
package Behaviors2 {
    item def Go;
    action def Abort {
        action warn send new Go() via ch;
        terminate;
    }
}
""")
w6b = diagrams.action_diagram(m6b.find("Behaviors2::Abort"))
w6b
Diagram (static snapshot of the interactive widget)

Static snapshot -- run the notebook in JupyterLab (pixi run lab) to interact.

term = nodes(w6b, "sysml-terminate")[0]
assert "glyph-x" in term.properties.shape.use  # the inscribed X

6c. Swim lanes — «performer» (printed p. 90)¶

action_diagram(..., lanes=True) derives dashed-boundary «performer» swim lanes from perform targets (perform station.a1 lands in lane station), ordered left-to-right; start/done markers stay outside.

spec_crop("spec-p121-perform-actions-swimlanes.png")
spec crop not available: spec-p121-perform-actions-swimlanes.png
m6c = longeron.loads("""
package Swimlanes {
    part station { action a1; action a4; }
    part rover { action a2; action a3; }
    action def Swim {
        perform station.a1;
        perform rover.a2;
        perform rover.a3;
        perform station.a4;
    }
}
""")
w6c = diagrams.action_diagram(m6c.find("Swimlanes::Swim"), lanes=True)
w6c
Diagram (static snapshot of the interactive widget)

Static snapshot -- run the notebook in JupyterLab (pixi run lab) to interact.

lanes = nodes(w6c, "sysml-lane")
assert {lane.labels[1].text for lane in lanes} == {"station", "rover"}
assert all(lane.labels[0].text == "«performer»" for lane in lanes)
assert w6c.source.value.layoutOptions["elk.partitioning.activate"] == "true"

6d. State machine — transitions + typed submachine expansion (printed pp. 115–116)¶

Hierarchical states with entry markers and trigger [guard] / effect transition labels. A state usage typed by a state def (state x : Inner;) expands into the definition’s submachine with instance-qualified ids (bound by submachine_depth).

spec_crop(
    "spec-p146-state-with-graphical-compartment-with-standard-state-transit.png",
    "spec-p147-transition.png",
)
spec crop not available: spec-p146-state-with-graphical-compartment-with-standard-state-transit.png
spec crop not available: spec-p147-transition.png
m6d = longeron.loads("""
package States {
    state def Inner {
        entry; then a;
        state a;
        transition first a accept go then b;
        state b;
    }
    state def Outer {
        entry; then x;
        state x : Inner;
        transition first x accept quit then off;
        state off;
    }
}
""")
w6d = diagrams.state_diagram(m6d.find("States::Outer"))
w6d
Diagram (static snapshot of the interactive widget)

Static snapshot -- run the notebook in JupyterLab (pixi run lab) to interact.

x = node(w6d, "States::Outer::x")
assert {"States::Outer::x::a", "States::Outer::x::b"} <= {n.id for n in walk(x) if n.id}
assert any("sysml-marker" in (n.properties.cssClasses or "") for n in walk(x))  # entry markers
texts = [lb.text for e in edges(w6d, "sysml-edge-transition") for lb in (e.labels or [])]
assert "go" in texts and "quit" in texts  # trigger labels

7. Annotations (printed pp. 20–21, 24, 157–158)¶

Every package box carries the spec’s folder tab riding its top-left (printed p. 24). annotations=True additionally draws comment/doc notes — the folded-corner box joined by a dashed anchor line with no endpoint glyph (printed pp. 20–21) — and «@Type» metadata adornments on annotated nodes (printed p. 157).

spec_crop(
    "spec-p55-package-name-in-body.png",
    "spec-p51-comment.png",
    "spec-p189-annotation-metadata.png",
)
spec crop not available: spec-p55-package-name-in-body.png
spec crop not available: spec-p51-comment.png
spec crop not available: spec-p189-annotation-metadata.png
m7 = longeron.loads("""
package Annotated {
    metadata def Safety;
    part def Pump {
        attribute pressure : Real;
    }
    @Safety about Pump;
    comment about Pump /* Centrifugal, oil-free. */
    part pump : Pump {
        doc /* The unit under review. */
    }
}
""")
w7 = diagrams.structure_diagram(m7, annotations=True)
w7
Diagram (static snapshot of the interactive widget)

Static snapshot -- run the notebook in JupyterLab (pixi run lab) to interact.

from longeron.render import _note_path_d

assert node(w7, "Annotated").labels[0].properties.shape.use == "package-tab"  # folder tab
notes = nodes(w7, "sysml-note")
assert {n.labels[0].text for n in notes} == {"«comment»", "«doc»"}
# the UML note silhouette: cut corner PLUS the crease 'L' outlining the
# fold triangle — one explicit path shared with the headless renderer
for n in notes:
    assert n.properties.shape.type == "node:path"
    assert n.properties.shape.use == _note_path_d(n.width, n.height)
anchors = edges(w7, "sysml-edge-anchor")
assert len(anchors) == 2 and all(e.properties.shape is None for e in anchors)  # dashed, no glyph
assert node(w7, "Annotated::Pump").labels[0].text == "«@Safety»"  # metadata adornment

8. Portions, actors, stakeholders (printed p. 52; errata N17)¶

8a. Portion membership — the notched ball (printed p. 52)¶

timeslice / snapshot usages draw a solid line with a filled ball, open-V notch on the line side, at the whole-occurrence end — replacing the plain typing edge. The «individual» / «timeslice» / «snapshot» keywords ride the boxes.

spec_crop(
    "spec-p83-time-slices-snapshots-and-portion-membership.png",
    "spec-p83-individual-occurrence.png",
)
spec crop not available: spec-p83-time-slices-snapshots-and-portion-membership.png
spec crop not available: spec-p83-individual-occurrence.png
m8a = longeron.loads("""
package Portions {
    individual part def Rover;
    individual rover : Rover;
    timeslice t1 : Rover;
    snapshot s1 : Rover;
}
""")
w8a = diagrams.structure_diagram(m8a)
w8a
Diagram (static snapshot of the interactive widget)

Static snapshot -- run the notebook in JupyterLab (pixi run lab) to interact.

portions = edges(w8a, "sysml-edge-portion")
assert {(e.source.id, e.target.id) for e in portions} == {
    ("Portions::t1", "Portions::Rover"),
    ("Portions::s1", "Portions::Rover"),
}
assert all(e.properties.shape.end == "portion-ball" for e in portions)
assert node(w8a, "Portions::t1").labels[0].text == "«timeslice»"
assert node(w8a, "Portions::Rover").labels[0].text == "«individual part def»"

8b. Actors and stakeholders — stick figure and keyword box¶

Actor usages draw the spec’s classic stick figure by default (BNF printed p.244; crop gt-actor.png): head circle + limbs line art in the usage palette, name below the figure — the «actor» keyword is omitted because the figure IS the stereotype. actor_style="box" keeps the errata-N17 «actor» keyword-box alternative (right). Stakeholders stay «stakeholder» keyword boxes in both styles — the spec reserves the figure for actors.

import ipywidgets as W

m8b = longeron.loads("""
package Actors {
    part def Person;
    use case def Deliver {
        subject route;
        actor driver : Person;
    }
    requirement def Comfort {
        stakeholder owner : Person;
    }
}
""")
w8b = diagrams.structure_diagram(m8b)  # actors draw the stick figure by default
w8b_box = diagrams.structure_diagram(m8b, actor_style="box")  # «actor» keyword-box alternative
W.HBox([w8b, w8b_box], layout=W.Layout(align_items="stretch"))
HBox (static snapshot of the interactive widget)

Static snapshot -- run the notebook in JupyterLab (pixi run lab) to interact.

driver = node(w8b, "Actors::Deliver::driver")  # present in the built tree, by qname
assert "sysml-actor" in (driver.properties.cssClasses or "")  # the stick-figure node class
assert driver.properties.shape.use.startswith("<circle")  # head circle + limbs line art
assert [lbl.text for lbl in driver.labels] == ["driver : Person"]  # name below, no «actor» row
assert node(w8b_box, "Actors::Deliver::driver").labels[0].text == "«actor»"  # box fallback
owner = node(w8b, "Actors::Comfort::owner")
assert owner.labels[0].text == "«stakeholder»"  # stakeholders stay keyword boxes

8c. View usages — «view» keyword box¶

A saved diagram’s recipe — a view usage, written by view persistence (longeron.views; design: docs/design/view-persistence.md) — is a model element too, so it draws where it lives: the generic usage box with the «view» keyword. The spec defines no dedicated view-usage glyph (views are diagram configuration, §7.26), so the keyword box IS the spec presentation; the view’s own members (expose, filter, render) configure the view and draw nothing themselves.

m8c = longeron.loads("""
package Rig {
    part def Axle { part hub : Hub [2]; }
    part def Hub;
    part axle : Axle;
    view 'axle structure' : StandardViewDefinitions::InterconnectionView {
        expose Rig::**;
        render Views::asInterconnectionDiagram;
    }
}
""")
w8c = diagrams.structure_diagram(m8c)
w8c
Diagram (static snapshot of the interactive widget)

Static snapshot -- run the notebook in JupyterLab (pixi run lab) to interact.

view_box = node(w8c, "Rig::axle structure")
assert "sysml-usage" in (view_box.properties.cssClasses or "")  # rounded usage box
stereo = [
    lbl.text for lbl in view_box.labels if "sysml-stereotype" in (lbl.properties.cssClasses or "")
]
assert stereo == ["«view»"]  # the keyword row
assert view_box.children == []  # expose/filter/render configure; they never draw

9. Edge routing styles (toolbar)¶

SysML tools (and the spec’s figures) mix straight and orthogonal connectors. Every widget’s toolbar carries a routing button (share-alt icon) that cycles orthogonal → polyline → splines and re-lays the live diagram out; the choice persists per widget on the tool’s routing trait. Headless renders take the same choice as the routing= kwarg. The option lands on the root and every compound node (ELK does not inherit it through INCLUDE_CHILDREN). Endpoint glyphs auto-orient, so the hollow triangles stay aligned on diagonal polyline shafts.

from longeron.toolbar import EdgeRoutingTool

w9 = diagrams.structure_diagram(m1a, routing="polyline")
tool = w9.get_tool(EdgeRoutingTool)
assert tool.routing == "POLYLINE"  # seeded by the kwarg, persisted per widget
assert w9.source.value.layoutOptions["elk.edgeRouting"] == "POLYLINE"
tool.ui.click()  # cycle: polyline -> splines
assert tool.routing == "SPLINES"
tool.routing = "orthogonal"  # the trait normalizes and re-applies
assert w9.source.value.layoutOptions["elk.edgeRouting"] == "ORTHOGONAL"
w9
Diagram (static snapshot of the interactive widget)

Static snapshot -- run the notebook in JupyterLab (pixi run lab) to interact.

10. Compartments — labeled sections + selectable rows (printed pp. 46, 60, 199)¶

A definition/usage node is its name compartment plus a compartment stack (spec 8.2.3.6, printed p.199): every labeled compartment opens with a full-width separator rule and its italic name — ‘attributes’ (printed p.46), ‘parts’ (p.60), ‘directed features’ (p.62), the constraint compartments (p.127), … And every row is a first-class selectable element: it carries the projected element’s qualified name as its id, so clicking a row selects that attribute exactly like clicking a node (hover shows the pointer cursor; the tree and inspector follow).

spec_crop("spec-p77-attributes-compartment.png", "spec-p91-parts-compartment.png")
spec crop not available: spec-p77-attributes-compartment.png
spec crop not available: spec-p91-parts-compartment.png
m10 = longeron.loads("""
package Comp {
    part def Battery {
        attribute capacity : Real = 5200.0;
        attribute cells : Integer = 3;
    }
    part def Drone {
        attribute mass : Real;
        part battery : Battery [2];
        constraint massLimit { mass < 2.0 }
    }
}
""")
w10a = diagrams.structure_diagram(m10)
w10a
Diagram (static snapshot of the interactive widget)

Static snapshot -- run the notebook in JupyterLab (pixi run lab) to interact.

battery = node(w10a, "Comp::Battery")
headers = [
    lab.text[2:]  # strip the fold twist the header affordance prepends
    for lab in battery.labels
    if "sysml-comp-label" in (lab.properties.cssClasses or "")
]
assert headers == ["attributes"]  # the labeled compartment replaces the row blob
row = next(lab for lab in battery.labels if (lab.text or "").startswith("capacity"))
assert row.id == "Comp::Battery::capacity"  # the row IS the attribute usage
assert row.properties.selectable is True  # click -> the same selection seam as nodes
drone = node(w10a, "Comp::Drone")
drone_headers = [
    lab.text[2:] for lab in drone.labels if "sysml-comp-label" in (lab.properties.cssClasses or "")
]
assert drone_headers == ["attributes", "constraints"]  # spec-ordered stack
print("compartments OK")
compartments OK

10b. Parts as rows — the collapsed presentation (printed p.60)¶

Nested parts legally render either as drawn nested boxes (the default — required where children anchor edges) or as textual name : Type rows in a parts compartment. parts="rows" picks the collapsed textual presentation; the rows keep full element identity.

w10b = diagrams.structure_diagram(m10, parts="rows")
w10b
Diagram (static snapshot of the interactive widget)

Static snapshot -- run the notebook in JupyterLab (pixi run lab) to interact.

drone_b = node(w10b, "Comp::Drone")
assert drone_b.children == []  # collapsed: no nested battery box
prow = next(lab for lab in drone_b.labels if lab.text == "battery : Battery [2]")
assert prow.id == "Comp::Drone::battery" and prow.properties.selectable is True
assert "parts" in [
    lab.text[2:]  # strip the fold twist the header affordance prepends
    for lab in drone_b.labels
    if "sysml-comp-label" in (lab.properties.cssClasses or "")
]
print("collapsed parts presentation OK")
collapsed parts presentation OK

Review checklist¶

Tick each glyph after checking both its resting and SELECTED look:

Specialization family (§1)

  • [ ] Subclassification :> — plain hollow triangle, solid line

  • [ ] Feature typing : — colon dots behind the head

  • [ ] Subsetting :> — plain hollow triangle

  • [ ] Redefinition :>> — perpendicular bar tick

  • [ ] Reference subsetting ::> — 2×2 colon dots

  • [ ] «satisfy requirement» box + reference-subsetting edge (longhand)

Membership (§2)

  • [ ] Composite membership — filled diamond, role name, [4] at the part end

  • [ ] Referential membership — hollow diamond

  • [ ] Owned membership — true circle-plus at the owning end (membership="edges")

  • [ ] Alias — hollow circle at the referencing end, alias name label

Connectors (§3)

  • [ ] Plain connect — no endpoint glyphs, cross multiplicities at the ends

  • [ ] Directed connection — open-V head, name : Type label

  • [ ] N-ary connection — filled junction dot, three spokes

  • [ ] Proxy connection — filled dot on the drawn ancestor, .part4 label INSIDE the box

  • [ ] Binding — = riding the line mid-span

  • [ ] Dependency — dashed open-V, (Uses) label

  • [ ] N-ary dependency — junction dot, plain client links

  • [ ] Satisfy shorthand — «satisfy» keyword edge

  • [ ] Allocate — «allocate» keyword edge

  • [ ] Named allocation — «allocation» box

Flows (§4)

  • [ ] Pin form — square pins straddling both borders, filled head at the target pin

  • [ ] Port-attached form — square-to-square, straight, filled head at the target square, item1 : Item1 riding the line

Ports (§5)

  • [ ] Direction squares — in / out / inout arrows, plain square without

  • [ ] Arrows read against the node interior — in points INTO the box on any side

  • [ ] Labels INSIDE the part body, next to the square

  • [ ] Conjugated ports — ~Pout receives (in arrow), ~Pin sends (out arrow)

  • [ ] Interface — attaches square-to-square

Behavior views (§6)

  • [ ] Start dot, done bullseye

  • [ ] Fork and join bars (edges distribute along the bar)

  • [ ] Decision and merge rhombi — fans converge on single anchor points

  • [ ] Accept and send badge boxes

  • [ ] Successions dashed, guards labeled [x > 0]

  • [ ] Terminate — circle-X

  • [ ] Swim lanes — dashed «performer» containers, left-to-right

  • [ ] State machine — entry marker, trigger-labeled transitions

  • [ ] Typed submachine expansion (x : Inner opens up)

Annotations (§7)

  • [ ] Package folder tab

  • [ ] Comment/doc notes — folded corner WITH the crease ‘L’, dashed glyph-free anchors

  • [ ] Metadata adornment — «@Safety»

Portions and actors (§8)

  • [ ] Portion membership — filled notched ball at the whole end

  • [ ] «individual» / «timeslice» / «snapshot» keywords

  • [ ] «actor» / «stakeholder» keyword boxes

Toolbar (§9)

  • [ ] Routing button (share-alt) cycles orthogonal → polyline → splines and re-lays out

  • [ ] Markers/adornments stay attached and aligned on diagonal polyline shafts

  • [ ] The choice persists per widget; routing= kwarg seeds it

Compartments (§10)

  • [ ] Separator rule spans the box edge-to-edge above each italic header

  • [ ] ‘attributes’ / ‘constraints’ / ‘parts’ headers stack in spec order

  • [ ] Rows hover with a pointer cursor and select in their own right (accent color)

  • [ ] parts="rows" collapses nested boxes to rows; the default stays nested

Next
Architecture
Previous
3D viewer
Copyright © 2025, sanbales
Made with Sphinx and @pradyunsg's Furo
On this page
  • Notation gallery — every implemented SysML v2 glyph
    • 1. Specialization family (printed pp. 36–37)
      • 1b. Satisfy longhand — «satisfy requirement» box + reference subsetting (printed p. 133)
    • 2. Membership (printed pp. 26, 38)
      • 2a. Feature membership — composite and referential diamonds (printed p. 38)
      • 2b. Owned membership — the circle-plus presentation (printed p. 26)
      • 2c. Unowned membership (alias) — hollow circle (printed p. 26)
    • 3. Connectors (printed pp. 19, 66–67, 79, 132)
      • 3a. Plain connect — no endpoint glyphs, cross multiplicities (printed p. 66)
      • 3b. Connection with direction indication (printed p. 66)
      • 3c. N-ary connection — junction dot (printed p. 66)
      • 3d. Proxy connection — dot on the shallowest drawn ancestor (printed p. 67)
      • 3e. Binding connector — the = glyph (printed p. 67)
      • 3f. Dependency — binary and n-ary (printed p. 19)
      • 3g. Satisfy shorthand — «satisfy» keyword edge (printed p. 132)
      • 3h. Allocation — «allocate» keyword edge + «allocation» box (printed p. 79)
    • 4. Flow connections (printed pp. 77, 81)
      • 4a. Pin form — undrawn ports (printed p. 81)
      • 4b. Port-attached form — filled head only (printed p. 77)
    • 5. Ports (printed pp. 59, 62, 75–76)
    • 6. Behavior views (printed pp. 90–92, 97, 115–116, 227)
      • 6a. Action flow — control glyphs, badges, dashed successions (printed pp. 92, 97, 227)
      • 6b. Terminate — circle-X (printed p. 227)
      • 6c. Swim lanes — «performer» (printed p. 90)
      • 6d. State machine — transitions + typed submachine expansion (printed pp. 115–116)
    • 7. Annotations (printed pp. 20–21, 24, 157–158)
    • 8. Portions, actors, stakeholders (printed p. 52; errata N17)
      • 8a. Portion membership — the notched ball (printed p. 52)
      • 8b. Actors and stakeholders — stick figure and keyword box
      • 8c. View usages — «view» keyword box
    • 9. Edge routing styles (toolbar)
    • 10. Compartments — labeled sections + selectable rows (printed pp. 46, 60, 199)
      • 10b. Parts as rows — the collapsed presentation (printed p.60)
    • Review checklist