Functional decomposition graph
disl0.50.5Functional decomposition graph
Example definition functional-decomposition.dis: A trimmed excerpt of the functional decomposition graph definition (definitions/diagrams/functional-decomposition-graph.dis), used to show DISL 0.2 constructs.
Views generated from the file
These views are drawn by this site from functional-decomposition.dis itself: its metamodel, and the layers it fills. They are not the notation the definition draws; that appears in an IDE that runs it.
functional-decomposition.dis at 4e69c28Diagram as text (Mermaid)
classDiagram
accTitle: Metamodel of the Functional decomposition graph example
accDescr: Class diagram of functional-decomposition.dis: 7 node types (FdgElement, NamedElement, UiElement, DataElement, Action, Function, Comment) and 7 relation types (FdgRelation, Owns, UiChild, OwnsAction, OwnsData, OwnsFunction, Shows), with their typed attributes, inheritance, containment and relation ends.
class FdgElement {
<<abstract>>
+text description
+number height
}
class NamedElement {
<<abstract>>
+string name
+number height
}
class UiElement {
}
class DataElement {
}
class Action {
}
class Function {
}
class Comment {
+text text
}
class FdgRelation {
<<relation>>
+string name
}
class Owns {
<<relation>>
}
class UiChild {
<<relation>>
}
class OwnsAction {
<<relation>>
}
class OwnsData {
<<relation>>
}
class OwnsFunction {
<<relation>>
}
class Shows {
<<relation>>
}
FdgElement <|-- NamedElement
NamedElement <|-- UiElement
NamedElement <|-- DataElement
NamedElement <|-- Action
NamedElement <|-- Function
FdgElement <|-- Comment
UiChild --> UiElement : source
UiChild --> UiElement : target
OwnsAction --> UiElement : source
OwnsAction --> Action : target
OwnsData --> UiElement : source
OwnsData --> Action : source
OwnsData --> DataElement : source
OwnsData --> DataElement : target
OwnsFunction --> UiElement : source
OwnsFunction --> Action : source
OwnsFunction --> DataElement : source
OwnsFunction --> Function : source
OwnsFunction --> Function : target
Shows --> Action : source
Shows --> UiElement : targetfunctional-decomposition.dis at 4e69c28Diagram as text (Mermaid)
flowchart LR
accTitle: Layers of functional-decomposition.dis
accDescr: The eight DISL layers in order. functional-decomposition.dis fills 7 of them: Metamodel (15 items), Coordinates (1 items), Notation (24 items), Toolbox (4 items), Constraints (8 items), Behavior (2 items), Persistence (5 items). It leaves Layout empty.
L1["1 · Metamodel<br/>15 items"]
L2["2 · Coordinate systems, placement and snapping<br/>1 items"]
L3["3 · Notation (visual definition)<br/>24 items"]
L4["4 · Toolbox, context tools and forms<br/>4 items"]
L5["5 · Constraints<br/>8 items"]
L6["6 · Behavior<br/>2 items"]
L7["7 · Layout<br/>not used"]
L8["8 · Persistence<br/>5 items"]
L1 --> L2 --> L3 --> L4 --> L5 --> L6 --> L7 --> L8
classDef filled stroke-width:3px
classDef empty stroke-dasharray:4 4
class L1 filled
class L2 filled
class L3 filled
class L4 filled
class L5 filled
class L6 filled
class L7 empty
class L8 filled- Layer 1 — Metamodel: 15 items
- Layer 2 — Coordinate systems, placement and snapping: 1 items
- Layer 3 — Notation (visual definition): 24 items
- Layer 4 — Toolbox, context tools and forms: 4 items
- Layer 5 — Constraints: 8 items
- Layer 6 — Behavior: 2 items
- Layer 7 — Layout: not used
- Layer 8 — Persistence: 5 items
The file
Download functional-decomposition.dis (16,150 bytes, unchanged from the source).
{
"$schema": "https://etalii.net/adp/disl/schema/0.2/disl.schema.json#/$defs/Specification",
"disl": "0.2",
"language": {
"id": "org.example.functional-decomposition",
"version": "0.1.0",
"origin": "etalii/functional-decomposition-graph",
"label": "Functional decomposition graph",
"doc": {
"summary": "A trimmed excerpt of the functional decomposition graph definition (definitions/diagrams/functional-decomposition-graph.dis), used to show DISL 0.2 constructs.",
"description": "An application broken down into its UI elements, the actions on them, the data they hold and the functions behind them, joined by ownership links. The excerpt keeps the element and relation hierarchies, their notation, the toolbox, the forms and the rules; the `.fdg` file format of the full definition is left out. It shows the superellipse, a custom diode shape, a contrast requirement, a height that is fixed and never stored, acyclicity over an abstract relation type, the built-in refusals worded by the tool, a deletion confirmation that asks only for connected elements, the findings form row and base-36 ShortGuids. It differs from the tool in three ways of its own: it names the ownership relations' common type `Owns` where the full definition says `Ownership`, it shows the fixed height as a read-only row and the findings under the properties, which the tool does not, and it leaves out what DISL 0.3 adds, such as the right-button drag between bodies that connects, the menus and the declared order of findings, which the full definition states."
}
},
"metamodel": {
"doc": "Five element types and five relation types. The four named element types are linked parent to child by the four ownership relations; Shows links an Action to the UI Element it shows; a Comment links to nothing.",
"types": {
"FdgElement": {
"abstract": true,
"attributes": {
"description": { "type": "text", "label": "Description", "markup": "plain" },
"height": { "type": "number", "label": "Height", "min": 48, "doc": "Stored only for a Comment; the four named types fix it." }
}
},
"NamedElement": {
"abstract": true,
"extends": "FdgElement",
"labelAttribute": "name",
"attributes": {
"name": { "type": "string", "label": "Name", "order": 0 },
"height": { "type": "number", "fixed": 48, "doc": "The four named types share one height of 48. A constant: read-only, never written to the .fdg, and a stored value is ignored." }
}
},
"UiElement": { "extends": "NamedElement", "label": "UI element" },
"DataElement": { "extends": "NamedElement", "label": "Data element" },
"Action": { "extends": "NamedElement", "label": "Action" },
"Function": { "extends": "NamedElement", "label": "Function" },
"Comment": {
"extends": "FdgElement",
"label": "Comment",
"labelAttribute": "text",
"attributes": {
"text": { "type": "text", "label": "Text", "markup": "plain", "order": 0 }
}
}
},
"relations": {
"FdgRelation": {
"abstract": true,
"directed": true,
"allowSelfLoops": false,
"attributes": {
"name": { "type": "string", "label": "Name", "order": 0 }
}
},
"Owns": {
"abstract": true,
"extends": "FdgRelation",
"acyclic": true,
"doc": "Ownership: every owned element has at most one owner. `acyclic` on this abstract type forbids a loop through any mix of its four subtypes, so no separate ownership-loop invariant is needed; Shows is not ownership and may close a navigation round trip."
},
"UiChild": {
"extends": "Owns",
"label": "UI child",
"source": "UiElement",
"target": { "types": ["UiElement"], "max": 1, "role": "child" }
},
"OwnsAction": {
"extends": "Owns",
"label": "Action",
"source": "UiElement",
"target": { "types": ["Action"], "max": 1, "role": "action" }
},
"OwnsData": {
"extends": "Owns",
"label": "Data",
"source": ["UiElement", "Action", "DataElement"],
"target": { "types": ["DataElement"], "max": 1, "role": "data" }
},
"OwnsFunction": {
"extends": "Owns",
"label": "Function",
"source": ["UiElement", "Action", "DataElement", "Function"],
"target": { "types": ["Function"], "max": 1, "role": "function" }
},
"Shows": {
"extends": "FdgRelation",
"label": "Shows",
"source": { "types": ["Action"], "max": 1, "role": "action" },
"target": { "types": ["UiElement"], "role": "shown" }
}
}
},
"coordinates": {
"systems": {
"canvas": {
"kind": "cartesian",
"x": { "kind": "linear" },
"y": { "kind": "linear" }
}
}
},
"notation": {
"theme": {
"tokens": {
"color.text": "#0f172a",
"color.border": "#e2e8f0",
"color.fdg.ui-element": "#aaed92",
"color.fdg.data-element": "#ededed",
"color.fdg.action": "#9edcfa",
"color.fdg.function": "#86e6d9",
"color.fdg.comment": "#fcf281"
},
"modes": {
"dark": {
"color.text": "#f1f5f9",
"color.border": "#334155",
"color.fdg.ui-element": "#2e7814",
"color.fdg.data-element": "#696969",
"color.fdg.action": "#086fa1",
"color.fdg.function": "#187569",
"color.fdg.comment": "#736a03"
}
},
"defaultMode": "light",
"followSystem": true,
"contrast": [
{
"foreground": "color.text",
"background": ["color.fdg.ui-element", "color.fdg.data-element", "color.fdg.action", "color.fdg.function", "color.fdg.comment"],
"minRatio": 4.5,
"doc": "The fills were chosen for text contrast against the text colour in both themes; a specification validator checks every pair in light and dark mode and warns below 4.5."
}
]
},
"styles": {
"element": {
"stroke": { "color": { "token": "color.border" }, "width": 1.5 },
"font": { "size": 12, "color": { "token": "color.text" } },
"textAlign": "center",
"verticalAlign": "middle"
},
"uiElement": { "extends": "element", "fill": { "token": "color.fdg.ui-element" } },
"dataElement": { "extends": "element", "fill": { "token": "color.fdg.data-element" } },
"action": { "extends": "element", "fill": { "token": "color.fdg.action" } },
"function": { "extends": "element", "fill": { "token": "color.fdg.function" } },
"comment": { "extends": "element", "fill": { "token": "color.fdg.comment" }, "textAlign": "left", "verticalAlign": "top" },
"link": {
"stroke": { "color": { "token": "color.text" }, "width": 1.5 },
"font": { "size": 12, "color": { "token": "color.text" } }
}
},
"shapes": {
"parallelogramRight": {
"label": "Parallelogram, top shifted right",
"path": {
"segments": [
{ "op": "M", "x": "w * 0.2", "y": 0 },
{ "op": "L", "x": "w", "y": 0 },
{ "op": "L", "x": "w * 0.8", "y": "h" },
{ "op": "L", "x": 0, "y": "h" },
{ "op": "Z" }
]
},
"textArea": { "x": "w * 0.15 + 6", "y": "h * 0.25", "w": "w * 0.7 - 12", "h": "h * 0.5" }
},
"diode": {
"label": "Diode",
"doc": "Square on the left and round on the right: a rectangle from the left edge to w - r, closed on the right by a semicircle of radius r = min(h / 2, w). A custom shape is exact here, so DISL needs no built-in for it.",
"path": {
"segments": [
{ "op": "M", "x": 0, "y": 0 },
{ "op": "L", "x": "w - min(h / 2.0, w)", "y": 0 },
{ "op": "A", "rx": "min(h / 2.0, w)", "ry": "min(h / 2.0, w)", "rotation": 0, "largeArc": false, "sweep": true, "x": "w - min(h / 2.0, w)", "y": "h" },
{ "op": "L", "x": 0, "y": "h" },
{ "op": "Z" }
]
},
"textArea": { "x": 6, "y": "h * 0.25", "w": "w - 0.134 * min(h / 2.0, w) - 12", "h": "h * 0.5" }
}
},
"nodes": {
"UiElement": {
"shape": { "type": "superellipse", "params": { "exponent": 4 } },
"style": "uiElement",
"size": { "default": [160, 48], "min": [80, 48], "max": [null, 48], "resizable": "horizontal" },
"placement": { "height": { "attribute": "height" } },
"labels": [ { "id": "name", "text": { "attribute": "name" }, "editable": "inline", "wrap": "none", "overflow": "ellipsis" } ],
"anchors": { "mode": "outline" },
"doc": "The built-in superellipse |2x/w - 1|^4 + |2y/h - 1|^4 = 1, which the 0.1 definition approximated with four cubic curves."
},
"DataElement": {
"shape": "parallelogramRight",
"style": "dataElement",
"size": { "default": [160, 48], "min": [80, 48], "max": [null, 48], "resizable": "horizontal" },
"labels": [ { "id": "name", "text": { "attribute": "name" }, "editable": "inline", "wrap": "none", "overflow": "ellipsis" } ],
"anchors": { "mode": "outline" }
},
"Action": {
"shape": { "type": "trapezoid", "params": { "inset": 0.15, "direction": "down" } },
"style": "action",
"size": { "default": [160, 48], "min": [80, 48], "max": [null, 48], "resizable": "horizontal" },
"labels": [ { "id": "name", "text": { "attribute": "name" }, "editable": "inline", "wrap": "none", "overflow": "ellipsis" } ],
"anchors": { "mode": "outline" }
},
"Function": {
"shape": "diode",
"style": "function",
"size": { "default": [160, 48], "min": [80, 48], "max": [null, 48], "resizable": "horizontal" },
"labels": [ { "id": "name", "text": { "attribute": "name" }, "editable": "inline", "wrap": "none", "overflow": "ellipsis" } ],
"anchors": { "mode": "outline" }
},
"Comment": {
"shape": "rect",
"style": "comment",
"size": { "default": [240, 96], "min": [80, 48], "resizable": true },
"placement": { "height": { "attribute": "height" } },
"labels": [ { "id": "text", "text": { "attribute": "text" }, "editable": "multiline", "wrap": "word", "maxWidth": "parent", "overflow": "ellipsis", "position": "top-left" } ],
"connectable": false,
"doc": "Resizable both ways; its height is the stored `height`, while the named types' height is the fixed 48."
}
},
"edges": {
"UiChild": {
"style": "link",
"line": { "routing": "bezier" },
"sourceMarker": "none",
"targetMarker": "arrowFilled",
"labels": [ { "id": "name", "text": { "attribute": "name" }, "at": "middle", "side": "above", "editable": "inline" } ]
},
"OwnsAction": { "style": "link", "line": { "routing": "bezier" }, "sourceMarker": "none", "targetMarker": "arrowFilled" },
"OwnsData": { "style": "link", "line": { "routing": "bezier" }, "sourceMarker": "none", "targetMarker": "arrowFilled" },
"OwnsFunction": { "style": "link", "line": { "routing": "bezier" }, "sourceMarker": "none", "targetMarker": "arrowFilled" },
"Shows": {
"style": "link",
"line": { "routing": "bezier" },
"sourceMarker": "none",
"targetMarker": "arrowFilled"
}
}
},
"forms": {
"namedElement": {
"for": ["UiElement", "DataElement", "Action", "Function"],
"usage": ["inspector"],
"items": [
{
"kind": "section",
"title": "Identity",
"items": [
{ "attribute": "name" },
{ "attribute": "description", "widget": "textarea", "rows": 3 },
{ "attribute": "height", "widget": "readonly", "readOnlyReasons": [ "Only a Comment has its own height; the others share one." ] }
]
},
{ "kind": "findings", "doc": "The findings on this element, listed under its properties (DISL 0.1 called this row kind `problems`)." }
]
}
},
"toolbox": {
"groups": [
{
"id": "elements",
"label": "Elements",
"tools": [
{ "id": "uiElement", "creates": "UiElement", "label": "UI element", "mode": "drop", "after": "select" },
{ "id": "action", "creates": "Action", "label": "Action", "mode": "drop", "after": "select" },
{ "id": "dataElement", "creates": "DataElement", "label": "Data element", "mode": "drop", "after": "select" },
{ "id": "function", "creates": "Function", "label": "Function", "mode": "drop", "after": "select" },
{ "id": "comment", "creates": "Comment", "label": "Comment", "mode": "drop", "after": "select", "initial": { "text": "New comment", "height": 96 } }
]
}
]
},
"constraints": {
"doc": "The rules are declared on the metamodel: the relations' endpoints and limits, no self-links, and acyclicity over the abstract Owns. A connect gesture is checked in DISL's fixed order - type (std.endpoints), then cardinality (std.multiplicity), then cycle (std.acyclic) - and the first refusal is shown, in the tool's own words. A document edited outside ADP that breaks the rules still opens, and each breach is reported.",
"blockSaveOn": "never",
"builtIn": {
"std.endpoints": { "severity": "error" },
"std.multiplicity": {
"severity": "error",
"refusal": { "cel": "violation.end == 'source' ? 'Refused by the cardinality check: `' + (source.name != '' ? source.name : source.id) + '` already has its `' + {'UiChild': 'ui-child', 'OwnsAction': 'owns-action', 'OwnsData': 'owns-data', 'OwnsFunction': 'owns-function', 'Shows': 'shows'}[violation.relationType] + '` link, and may have ' + string(violation.max) + '.' : 'Refused by the cardinality check: `' + (target.name != '' ? target.name : target.id) + '` already has its `' + {'UiChild': 'ui-child', 'OwnsAction': 'owns-action', 'OwnsData': 'owns-data', 'OwnsFunction': 'owns-function', 'Shows': 'shows'}[violation.relationType] + '` parent, and may have ' + string(violation.max) + '.'" },
"doc": "The refusal of the connect gesture, which sees `violation`; findings on a stored document keep the runtime's wording. Covers fdg.second-parent and fdg.second-shows."
},
"std.acyclic": {
"severity": "error",
"code": "fdg.ownership-cycle",
"refusal": { "cel": "'Refused by the cycle check: `' + (target.name != '' ? target.name : target.id) + '` already owns `' + (source.name != '' ? source.name : source.id) + '`, so this link would make an ownership loop.'" },
"doc": "Over Owns and all four of its subtypes; `violation.cycle` lists the loop."
},
"std.references": { "severity": "error", "code": "fdg.dangling-reference" },
"std.duplicateId": { "severity": "error", "code": "fdg.duplicate-id" },
"std.unreadableEntry": { "code": "fdg.unreadable-entry" }
}
},
"behavior": {
"deletion": {
"NamedElement": {
"relations": "delete",
"confirm": {
"count": "self.incoming.size() + self.outgoing.filter(r, r.target != self).size()",
"threshold": 1,
"title": "Remove",
"message": { "cel": "'Removing this element also removes the ' + (count == 1 ? '1 connection' : string(count) + ' connections') + ' to or from it.'" },
"confirmLabel": "Remove",
"danger": true,
"doc": "Asks only when the element has connections, naming how many; an unconnected element goes without a question."
}
},
"Comment": { "relations": "delete" }
}
},
"persistence": {
"ids": {
"strategy": "uuid-v4",
"encoding": "base36",
"stable": true,
"doc": "Ids ADP creates are random GUIDs written as 25 lowercase base-36 characters. Hand-authored ids, such as task-detail, are kept."
},
"view": { "store": ["bounds"] }
}
}In an IDE
No IDE host runs this example yet.