@dxos/diagram 0.11.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/chunk-parse.mjs +9406 -0
- package/dist/lib/chunk-parse.mjs.map +1 -0
- package/dist/lib/extension.mjs +204 -0
- package/dist/lib/extension.mjs.map +1 -0
- package/dist/lib/index.mjs +497 -2846
- package/dist/lib/index.mjs.map +1 -1
- package/dist/types/src/aesthetics.d.ts +12 -0
- package/dist/types/src/aesthetics.d.ts.map +1 -0
- package/dist/types/src/aesthetics.test.d.ts +2 -0
- package/dist/types/src/aesthetics.test.d.ts.map +1 -0
- package/dist/types/src/appeal.d.ts +55 -0
- package/dist/types/src/appeal.d.ts.map +1 -0
- package/dist/types/src/appeal.test.d.ts +2 -0
- package/dist/types/src/appeal.test.d.ts.map +1 -0
- package/dist/types/src/architecture.d.ts +86 -0
- package/dist/types/src/architecture.d.ts.map +1 -0
- package/dist/types/src/architecture.test.d.ts +2 -0
- package/dist/types/src/architecture.test.d.ts.map +1 -0
- package/dist/types/src/compact.d.ts +39 -0
- package/dist/types/src/compact.d.ts.map +1 -0
- package/dist/types/src/compact.test.d.ts +2 -0
- package/dist/types/src/compact.test.d.ts.map +1 -0
- package/dist/types/src/diagnostics.d.ts +70 -3
- package/dist/types/src/diagnostics.d.ts.map +1 -1
- package/dist/types/src/dsl/cli.d.ts +2 -0
- package/dist/types/src/dsl/cli.d.ts.map +1 -0
- package/dist/types/src/dsl/compile.d.ts +12 -0
- package/dist/types/src/dsl/compile.d.ts.map +1 -0
- package/dist/types/src/dsl/convert.d.ts +45 -0
- package/dist/types/src/dsl/convert.d.ts.map +1 -0
- package/dist/types/src/dsl/convert.test.d.ts +2 -0
- package/dist/types/src/dsl/convert.test.d.ts.map +1 -0
- package/dist/types/src/dsl/dsl.test.d.ts +2 -0
- package/dist/types/src/dsl/dsl.test.d.ts.map +1 -0
- package/dist/types/src/dsl/gen/diagram.d.ts +3 -0
- package/dist/types/src/dsl/gen/diagram.d.ts.map +1 -0
- package/dist/types/src/dsl/index.d.ts +7 -0
- package/dist/types/src/dsl/index.d.ts.map +1 -0
- package/dist/types/src/dsl/parse.d.ts +49 -0
- package/dist/types/src/dsl/parse.d.ts.map +1 -0
- package/dist/types/src/dsl/print.d.ts +19 -0
- package/dist/types/src/dsl/print.d.ts.map +1 -0
- package/dist/types/src/dsl/semantic.test.d.ts +2 -0
- package/dist/types/src/dsl/semantic.test.d.ts.map +1 -0
- package/dist/types/src/dsl/vocabulary.d.ts +61 -0
- package/dist/types/src/dsl/vocabulary.d.ts.map +1 -0
- package/dist/types/src/dx-svg.d.ts +12 -0
- package/dist/types/src/dx-svg.d.ts.map +1 -0
- package/dist/types/src/dx-svg.test.d.ts +2 -0
- package/dist/types/src/dx-svg.test.d.ts.map +1 -0
- package/dist/types/src/extension/complete.d.ts +3 -0
- package/dist/types/src/extension/complete.d.ts.map +1 -0
- package/dist/types/src/extension/extension.test.d.ts +2 -0
- package/dist/types/src/extension/extension.test.d.ts.map +1 -0
- package/dist/types/src/extension/highlight.d.ts +14 -0
- package/dist/types/src/extension/highlight.d.ts.map +1 -0
- package/dist/types/src/extension/index.d.ts +6 -0
- package/dist/types/src/extension/index.d.ts.map +1 -0
- package/dist/types/src/extension/language.d.ts +13 -0
- package/dist/types/src/extension/language.d.ts.map +1 -0
- package/dist/types/src/extension/lint.d.ts +21 -0
- package/dist/types/src/extension/lint.d.ts.map +1 -0
- package/dist/types/src/extension/syntax.d.ts +3 -0
- package/dist/types/src/extension/syntax.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +14 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/mermaid-engine.d.ts +40 -1
- package/dist/types/src/mermaid-engine.d.ts.map +1 -1
- package/dist/types/src/mermaid.d.ts +3 -3
- package/dist/types/src/mermaid.d.ts.map +1 -1
- package/dist/types/src/nudge.d.ts +30 -0
- package/dist/types/src/nudge.d.ts.map +1 -0
- package/dist/types/src/nudge.test.d.ts +2 -0
- package/dist/types/src/nudge.test.d.ts.map +1 -0
- package/dist/types/src/objective.d.ts +4 -0
- package/dist/types/src/objective.d.ts.map +1 -1
- package/dist/types/src/ortho-router.d.ts +13 -1
- package/dist/types/src/ortho-router.d.ts.map +1 -1
- package/dist/types/src/ports.d.ts +40 -0
- package/dist/types/src/ports.d.ts.map +1 -0
- package/dist/types/src/ports.test.d.ts +2 -0
- package/dist/types/src/ports.test.d.ts.map +1 -0
- package/dist/types/src/relations.test.d.ts +2 -0
- package/dist/types/src/relations.test.d.ts.map +1 -0
- package/dist/types/src/rules.d.ts +30 -0
- package/dist/types/src/rules.d.ts.map +1 -0
- package/dist/types/src/scene.d.ts +108 -74
- package/dist/types/src/scene.d.ts.map +1 -1
- package/dist/types/src/score.d.ts +54 -0
- package/dist/types/src/score.d.ts.map +1 -0
- package/dist/types/src/score.test.d.ts +2 -0
- package/dist/types/src/score.test.d.ts.map +1 -0
- package/dist/types/src/semantic-engine.d.ts +41 -0
- package/dist/types/src/semantic-engine.d.ts.map +1 -0
- package/dist/types/src/semantic-engine.test.d.ts +2 -0
- package/dist/types/src/semantic-engine.test.d.ts.map +1 -0
- package/dist/types/src/semantic-place.d.ts +81 -0
- package/dist/types/src/semantic-place.d.ts.map +1 -0
- package/dist/types/src/semantic-route.d.ts +133 -0
- package/dist/types/src/semantic-route.d.ts.map +1 -0
- package/dist/types/src/semantic.d.ts +164 -0
- package/dist/types/src/semantic.d.ts.map +1 -0
- package/dist/types/src/uml-grid.d.ts.map +1 -1
- package/dist/types/src/uml.d.ts +3 -5
- package/dist/types/src/uml.d.ts.map +1 -1
- package/dist/types/src/view.d.ts +53 -0
- package/dist/types/src/view.d.ts.map +1 -0
- package/dist/types/src/view.test.d.ts +2 -0
- package/dist/types/src/view.test.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +23 -5
- package/rules/DIAGRAM.mdl +388 -0
- package/src/aesthetics.test.ts +89 -0
- package/src/aesthetics.ts +148 -0
- package/src/appeal.test.ts +144 -0
- package/src/appeal.ts +586 -0
- package/src/architecture.test.ts +94 -0
- package/src/architecture.ts +215 -0
- package/src/compact.test.ts +67 -0
- package/src/compact.ts +219 -0
- package/src/diagnostics.test.ts +207 -1
- package/src/diagnostics.ts +481 -37
- package/src/dsl/cli.ts +46 -0
- package/src/dsl/compile.ts +28 -0
- package/src/dsl/convert.test.ts +51 -0
- package/src/dsl/convert.ts +98 -0
- package/src/dsl/diagram.grammar +195 -0
- package/src/dsl/dsl.test.ts +286 -0
- package/src/dsl/gen/diagram.ts +22 -0
- package/src/dsl/index.ts +44 -0
- package/src/dsl/parse.ts +1054 -0
- package/src/dsl/print.ts +198 -0
- package/src/dsl/semantic.test.ts +216 -0
- package/src/dsl/vocabulary.ts +150 -0
- package/src/dx-svg.test.ts +44 -0
- package/src/dx-svg.ts +77 -0
- package/src/extension/complete.ts +105 -0
- package/src/extension/extension.test.ts +135 -0
- package/src/extension/highlight.ts +23 -0
- package/src/extension/index.ts +9 -0
- package/src/extension/language.ts +31 -0
- package/src/extension/lint.ts +60 -0
- package/src/extension/syntax.ts +62 -0
- package/src/index.ts +14 -0
- package/src/mermaid-engine.test.ts +6 -6
- package/src/mermaid-engine.ts +569 -119
- package/src/mermaid.test.ts +17 -1
- package/src/mermaid.ts +30 -18
- package/src/nudge.test.ts +186 -0
- package/src/nudge.ts +387 -0
- package/src/objective.test.ts +2 -0
- package/src/objective.ts +17 -1
- package/src/ortho-router.ts +240 -80
- package/src/ports.test.ts +157 -0
- package/src/ports.ts +186 -0
- package/src/relations.test.ts +98 -0
- package/src/rules.ts +117 -0
- package/src/scene.ts +66 -5
- package/src/score.test.ts +64 -0
- package/src/score.ts +130 -0
- package/src/semantic-engine.test.ts +635 -0
- package/src/semantic-engine.ts +891 -0
- package/src/semantic-place.ts +1508 -0
- package/src/semantic-route.ts +1136 -0
- package/src/semantic.ts +278 -0
- package/src/typings.d.ts +8 -0
- package/src/uml-grid.ts +6 -1
- package/src/uml.test.ts +5 -1
- package/src/uml.ts +24 -11
- package/src/view.test.ts +139 -0
- package/src/view.ts +376 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/diagram",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Renderer-neutral scene DSL, diagram dialects, and layout engines.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -18,6 +18,11 @@
|
|
|
18
18
|
"types": "./dist/types/src/index.d.ts",
|
|
19
19
|
"import": "./dist/lib/index.mjs"
|
|
20
20
|
},
|
|
21
|
+
"./extension": {
|
|
22
|
+
"source": "./src/extension/index.ts",
|
|
23
|
+
"types": "./dist/types/src/extension/index.d.ts",
|
|
24
|
+
"import": "./dist/lib/extension.mjs"
|
|
25
|
+
},
|
|
21
26
|
"./testing": {
|
|
22
27
|
"source": "./src/testing.ts",
|
|
23
28
|
"types": "./dist/types/src/testing.d.ts",
|
|
@@ -28,19 +33,32 @@
|
|
|
28
33
|
"types": "dist/types/src/index.d.ts",
|
|
29
34
|
"files": [
|
|
30
35
|
"dist",
|
|
36
|
+
"rules",
|
|
31
37
|
"src"
|
|
32
38
|
],
|
|
33
39
|
"dependencies": {
|
|
40
|
+
"@codemirror/autocomplete": "^6.20.3",
|
|
41
|
+
"@codemirror/language": "^6.12.4",
|
|
42
|
+
"@codemirror/lint": "6.9.7",
|
|
43
|
+
"@codemirror/state": "^6.7.6",
|
|
44
|
+
"@codemirror/view": "^6.43.13",
|
|
34
45
|
"@dagrejs/dagre": "^3.1.1",
|
|
46
|
+
"@lezer/common": "^1.5.2",
|
|
47
|
+
"@lezer/highlight": "^1.2.3",
|
|
48
|
+
"@lezer/lr": "^1.4.10",
|
|
35
49
|
"elkjs": "^0.12.0",
|
|
36
|
-
"@dxos/
|
|
37
|
-
"@dxos/
|
|
50
|
+
"@dxos/effect": "0.14.0",
|
|
51
|
+
"@dxos/invariant": "0.14.0",
|
|
52
|
+
"@dxos/util": "0.14.0"
|
|
38
53
|
},
|
|
39
54
|
"devDependencies": {
|
|
40
|
-
"
|
|
55
|
+
"@lezer/generator": "^1.8.0",
|
|
56
|
+
"@uiw/codemirror-theme-vscode": "^4.25.2",
|
|
57
|
+
"effect": "4.0.0",
|
|
58
|
+
"tsx": "^4.21.0"
|
|
41
59
|
},
|
|
42
60
|
"peerDependencies": {
|
|
43
|
-
"effect": "4.0.0
|
|
61
|
+
"effect": "4.0.0"
|
|
44
62
|
},
|
|
45
63
|
"publishConfig": {
|
|
46
64
|
"access": "public"
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: org.dxos.diagram.rules
|
|
3
|
+
name: Diagram rules
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Diagram rules
|
|
8
|
+
|
|
9
|
+
What makes an architecture diagram read well to a person, as rules a layout can be scored against.
|
|
10
|
+
`@dxos/diagram` loads this file (`Rules.RULES`); `Appeal` measures every `code` and `both` rule from the
|
|
11
|
+
scene's geometry, and the image judge asks every `vision` and `both` rule of a decision model shown
|
|
12
|
+
the rendered page. `weight` ranks the rules by the strength of the evidence behind them: crossings
|
|
13
|
+
first in every study that measured them, then orthogonality and flow, path continuity, bends, and
|
|
14
|
+
only then alignment, symmetry and compactness, which people prefer but trade against the rest.
|
|
15
|
+
|
|
16
|
+
Rules about what a diagram says (a title, a legend, labels naming technology, one level of
|
|
17
|
+
abstraction) are not here: they belong to `Architecture.RULES`, which judges content, and a layout
|
|
18
|
+
engine cannot change them.
|
|
19
|
+
|
|
20
|
+
Evidence keys used in `Source:` lines:
|
|
21
|
+
|
|
22
|
+
- Purchase 1997: "Which aesthetic has the greatest effect on human understanding?", GD'97, LNCS 1353.
|
|
23
|
+
- Purchase 2002: Purchase, Carrington & Allder, "Empirical evaluation of aesthetics-based graph layout",
|
|
24
|
+
Empirical Software Engineering 7(3), and "Graph layout aesthetics in UML diagrams", JGAA 6(3).
|
|
25
|
+
- Ware 2002: Ware, Purchase, Colpoys & McGill, "Cognitive measurements of graph aesthetics",
|
|
26
|
+
Information Visualization 1.
|
|
27
|
+
- HOLA: Kieffer, Dwyer, Marriott & Wybrow, "HOLA: Human-like orthogonal network layout", IEEE TVCG
|
|
28
|
+
22(1), 2016 (InfoVis 2015).
|
|
29
|
+
- Ware 2020: Colin Ware, *Information Visualization: Perception for Design*, 4th ed., ch. 6 (Gestalt).
|
|
30
|
+
- Imhof 1975: "Positioning names on maps", The American Cartographer 2(2).
|
|
31
|
+
- C4: Simon Brown, the C4 model notation and diagram review checklist, https://c4model.com.
|
|
32
|
+
- Tufte: *The Visual Display of Quantitative Information* (data-ink) and *Envisioning Information*.
|
|
33
|
+
- Bertin: *Semiology of Graphics* (visual variables).
|
|
34
|
+
|
|
35
|
+
## Extensions
|
|
36
|
+
|
|
37
|
+
| Term | URI |
|
|
38
|
+
| -------------- | ----------------------------- |
|
|
39
|
+
| `diagram-rule` | org.dxos.mdl.diagram-rule@1.0 |
|
|
40
|
+
|
|
41
|
+
## Crossings and edges
|
|
42
|
+
|
|
43
|
+
```mdl
|
|
44
|
+
diagram-rule no-edge-crossings: Arrows do not cross each other
|
|
45
|
+
category: crossings
|
|
46
|
+
evaluator: both
|
|
47
|
+
weight: 10
|
|
48
|
+
question: Can every arrow be followed without it crossing another arrow?
|
|
49
|
+
yes: No two arrows cross, or one crossing is unavoidable and clearly at a right angle.
|
|
50
|
+
no: Several arrows cross each other.
|
|
51
|
+
The most damaging aesthetic in every study that measured it: it slows tracing and causes misreads.
|
|
52
|
+
Code: the number of properly crossing connector pairs, scored 1 / (1 + crossings / 2).
|
|
53
|
+
Source: Purchase 1997; Purchase 2002; Ware 2002; HOLA.
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```mdl
|
|
57
|
+
diagram-rule no-edge-through-node: Arrows never pass through a box they do not connect
|
|
58
|
+
category: edges
|
|
59
|
+
evaluator: code
|
|
60
|
+
weight: 8
|
|
61
|
+
An arrow running across a box reads as a connection to it ("edge tunnelling").
|
|
62
|
+
Code: 1 when no connector has an interior run through a box other than a frame, else 0.
|
|
63
|
+
Source: Dunne et al. 2015, IBM J. R&D 59; libavoid (Wybrow, Marriott & Stuckey 2010).
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```mdl
|
|
67
|
+
diagram-rule no-shared-trunks: Distinct arrows never run along the same line
|
|
68
|
+
category: edges
|
|
69
|
+
evaluator: both
|
|
70
|
+
weight: 6
|
|
71
|
+
question: Does every arrow have its own line, with no two arrows merged into one shared trunk?
|
|
72
|
+
yes: Each arrow runs on its own line; parallel arrows keep a visible gap.
|
|
73
|
+
no: Two or more arrows share a segment, so their ends cannot be paired up.
|
|
74
|
+
Overlapping segments make it impossible to tell which arrow goes where; routers nudge parallel runs
|
|
75
|
+
apart for this reason. A deliberate merge of same-meaning arrows into one target (an inheritance
|
|
76
|
+
bus) does not count.
|
|
77
|
+
Code: connector pairs that share a collinear run, scored 1 / (1 + pairs).
|
|
78
|
+
Source: Wybrow et al. 2010 (nudging); Purchase 2002 ("no adjacent arrows" preferred).
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```mdl
|
|
82
|
+
diagram-rule edges-head-toward-target: Each arrow takes a direct route to its target
|
|
83
|
+
category: edges
|
|
84
|
+
evaluator: both
|
|
85
|
+
weight: 5
|
|
86
|
+
question: Does each arrow run fairly directly from its source to its target, without long detours or doubling back?
|
|
87
|
+
yes: Arrows take short, direct routes.
|
|
88
|
+
no: Some arrows loop around the page, leave in the wrong direction, or double back.
|
|
89
|
+
Readers predict where an arrow ends from the way it leaves its source, and path length is the
|
|
90
|
+
strongest predictor of tracing time.
|
|
91
|
+
Code: per connector, route length over the gap between its two boxes (at least 64 units); the share
|
|
92
|
+
of connectors within twice that, softened by the mean excess. Measured against the boxes rather than
|
|
93
|
+
the route's own ends, so a loop round the page and a pair of related boxes placed far apart both count.
|
|
94
|
+
Source: Huang, Eades & Hong 2009 (geodesic-path tendency); Ware 2002.
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
```mdl
|
|
98
|
+
diagram-rule few-bends: Each arrow bends no more than its ends require
|
|
99
|
+
category: edges
|
|
100
|
+
evaluator: code
|
|
101
|
+
weight: 4
|
|
102
|
+
Bends rank below crossings, orthogonality and flow, but each one costs tracing time. Zero bends
|
|
103
|
+
when the boxes line up, one for an L, two for a Z.
|
|
104
|
+
Code: mean bends per connector beyond the minimum its end points allow, scored 1 / (1 + excess).
|
|
105
|
+
Source: Purchase 1997; Purchase 2002; Tamassia 1987.
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
```mdl
|
|
109
|
+
diagram-rule straight-when-aligned: Boxes that face each other are joined by a straight arrow
|
|
110
|
+
category: edges
|
|
111
|
+
evaluator: code
|
|
112
|
+
weight: 3
|
|
113
|
+
When two connected boxes overlap across the flow, the arrow between them should be one straight
|
|
114
|
+
segment: the cheapest bend saved, and collinearity aids recall.
|
|
115
|
+
Code: among connectors whose end boxes overlap horizontally or vertically, the share drawn with no bend.
|
|
116
|
+
Source: ELK Brandes-Köpf straightening; Marriott et al. 2012 (collinearity aids recall).
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
```mdl
|
|
120
|
+
diagram-rule uniform-edge-length: Arrows have similar lengths
|
|
121
|
+
category: edges
|
|
122
|
+
evaluator: code
|
|
123
|
+
weight: 2
|
|
124
|
+
Human-drawn layouts have uniform edge lengths, and their spread correlates with dislike.
|
|
125
|
+
Code: 1 − min(1, coefficient of variation of connector lengths).
|
|
126
|
+
Source: HOLA (R8); Dwyer et al. 2009; Purchase, Pilcher & Plimmer 2012.
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Flow
|
|
130
|
+
|
|
131
|
+
```mdl
|
|
132
|
+
diagram-rule consistent-flow: Most arrows run the same way
|
|
133
|
+
category: flow
|
|
134
|
+
evaluator: both
|
|
135
|
+
weight: 5
|
|
136
|
+
question: Do nearly all arrows run in one main direction (down, or left to right), with only a few pointing back?
|
|
137
|
+
yes: The arrows share one direction; a reader follows dependencies one way.
|
|
138
|
+
no: Arrows run in mixed directions, or whole chains point back against the flow.
|
|
139
|
+
Information flow ranks third for software diagrams, after crossings and orthogonality. Arrows with
|
|
140
|
+
a hollow triangle head point to an abstraction above by convention and do not count against it.
|
|
141
|
+
Code: the share of connectors whose net displacement runs with the diagram's dominant direction.
|
|
142
|
+
Source: Purchase 2002; Sugiyama, Tagawa & Toda 1981.
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
```mdl
|
|
146
|
+
diagram-rule continuous-paths: A chain through a box continues on its far side
|
|
147
|
+
category: flow
|
|
148
|
+
evaluator: code
|
|
149
|
+
weight: 2
|
|
150
|
+
A box with one arrow in and one out reads as a step on a path when the arrow leaves on the side
|
|
151
|
+
opposite the one it entered by; turning back on the same side breaks the line of reading.
|
|
152
|
+
Code: for boxes with one incoming and one outgoing connector, 1 for opposite sides, 0.5 adjacent, 0 same.
|
|
153
|
+
Source: Ware 2002 (continuity); HOLA (R2).
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Alignment
|
|
157
|
+
|
|
158
|
+
```mdl
|
|
159
|
+
diagram-rule grid-alignment: Boxes line up in rows and columns
|
|
160
|
+
category: alignment
|
|
161
|
+
evaluator: both
|
|
162
|
+
weight: 3
|
|
163
|
+
question: Do the boxes line up in clear rows and columns?
|
|
164
|
+
yes: Most boxes share a row or column with two or more others.
|
|
165
|
+
no: Boxes are staggered, with few clear rows or columns.
|
|
166
|
+
People prefer grid-like placement and choose it when they draw by hand.
|
|
167
|
+
Code: the share of boxes whose centre lines up with at least two others horizontally or vertically.
|
|
168
|
+
Source: HOLA (R4); Purchase, Pilcher & Plimmer 2012.
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
```mdl
|
|
172
|
+
diagram-rule no-near-misses: Boxes are either aligned or clearly apart
|
|
173
|
+
category: alignment
|
|
174
|
+
evaluator: code
|
|
175
|
+
weight: 2
|
|
176
|
+
Two boxes almost in line look like a mistake; it is the most visible sign of an automatic layout.
|
|
177
|
+
Code: box pairs whose centres or edges differ by more than 1 and less than 12 units along an axis,
|
|
178
|
+
scored 1 / (1 + pairs / 2).
|
|
179
|
+
Source: HOLA (the gap between human and algorithmic layouts).
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
```mdl
|
|
183
|
+
diagram-rule symmetry: Symmetric structure is drawn symmetrically
|
|
184
|
+
category: alignment
|
|
185
|
+
evaluator: vision
|
|
186
|
+
weight: 1
|
|
187
|
+
question: Where the structure is symmetric, such as a fan-out to similar parts, is it drawn mirror-symmetrically?
|
|
188
|
+
yes: Fan-outs and parallel paths are drawn evenly about an axis.
|
|
189
|
+
no: Parts that play the same role are drawn lopsided or scattered.
|
|
190
|
+
Symmetry aids understanding, recall and preference, though less than crossings.
|
|
191
|
+
Source: Purchase 1997; Marriott et al. 2012; HOLA (R5).
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Groups
|
|
195
|
+
|
|
196
|
+
```mdl
|
|
197
|
+
diagram-rule frames-nested: Group frames are either nested or apart, never partly overlapping
|
|
198
|
+
category: groups
|
|
199
|
+
evaluator: code
|
|
200
|
+
weight: 8
|
|
201
|
+
Enclosure is the strongest grouping cue; a frame that partly overlaps another states no hierarchy.
|
|
202
|
+
Code: 1 when every pair of frames is disjoint or one contains the other, else 0.
|
|
203
|
+
Source: Palmer 1992 (common region); Ware 2020.
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
```mdl
|
|
207
|
+
diagram-rule frames-aligned: Groups line up with each other
|
|
208
|
+
category: groups
|
|
209
|
+
evaluator: both
|
|
210
|
+
weight: 3
|
|
211
|
+
question: Do the group frames line up with one another, sharing a top or left edge where they sit side by side or stacked?
|
|
212
|
+
yes: Groups share edges and read as one arrangement.
|
|
213
|
+
no: A group floats at an arbitrary height or offset beside another.
|
|
214
|
+
Aligned bands read as layers; ragged frames look accidental.
|
|
215
|
+
Code: the share of frames that share a top, bottom, left or right edge with another frame.
|
|
216
|
+
Source: HOLA (gridiness); Tufte, *Envisioning Information* (layering and separation).
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
```mdl
|
|
220
|
+
diagram-rule frames-hug-members: Group frames fit their contents
|
|
221
|
+
category: groups
|
|
222
|
+
evaluator: code
|
|
223
|
+
weight: 2
|
|
224
|
+
A frame much larger than what it holds suggests missing members and wastes space.
|
|
225
|
+
Code: per frame, the area of its members' bounding box over the frame's area, averaged and scaled
|
|
226
|
+
so that 0.5 or more scores 1.
|
|
227
|
+
Source: HOLA (R3 compactness); ELK padding.
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
```mdl
|
|
231
|
+
diagram-rule few-frame-crossings: Arrows cross group borders no more than they must
|
|
232
|
+
category: groups
|
|
233
|
+
evaluator: code
|
|
234
|
+
weight: 3
|
|
235
|
+
Every border crossing is noise, and an arrow that leaves and re-enters a group misstates membership.
|
|
236
|
+
An arrow should cross only the borders between its two ends' groups.
|
|
237
|
+
Code: border crossings beyond the minimum the group tree requires, scored 1 / (1 + extra / 2).
|
|
238
|
+
Source: Eades, Feng & Lin 1996 (clustered graphs); Ware 2020 (connectedness and enclosure).
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
```mdl
|
|
242
|
+
diagram-rule gestalt-cues-agree: Each box's nearest neighbour is in the same group
|
|
243
|
+
category: groups
|
|
244
|
+
evaluator: code
|
|
245
|
+
weight: 3
|
|
246
|
+
When a box sits closer to a box in another group than to any in its own, proximity contradicts the
|
|
247
|
+
frame and readers misread the grouping.
|
|
248
|
+
Code: the share of boxes whose nearest other box shares their innermost frame.
|
|
249
|
+
Source: Ware 2020 ch. 6; Palmer & Rock 1994.
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Space
|
|
253
|
+
|
|
254
|
+
```mdl
|
|
255
|
+
diagram-rule compact-drawing: The drawing uses its area well
|
|
256
|
+
category: space
|
|
257
|
+
evaluator: both
|
|
258
|
+
weight: 3
|
|
259
|
+
question: Is the drawing compact, with no large empty regions between or inside groups?
|
|
260
|
+
yes: The page is evenly filled; connected boxes are near each other.
|
|
261
|
+
no: There are large empty regions, stretched groups, or connected boxes far apart.
|
|
262
|
+
Compactness correlates with preference, and spread lengthens arrows.
|
|
263
|
+
Code: box area over the drawing's area, scored against a target of 0.25 (1 at or above it).
|
|
264
|
+
Source: HOLA (R3); Ware 2002.
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
```mdl
|
|
268
|
+
diagram-rule balanced-whitespace: Ink is spread evenly across the page
|
|
269
|
+
category: space
|
|
270
|
+
evaluator: code
|
|
271
|
+
weight: 2
|
|
272
|
+
A crowded half next to an empty one looks unbalanced and suggests a grouping that is not there.
|
|
273
|
+
Code: split the drawing into a 3 × 3 grid; 1 − min(1, CV / 1.5), with CV the coefficient of variation of box area per cell.
|
|
274
|
+
Source: HOLA participant comments; Ngo, Teo & Byrne 2003 (balance).
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
```mdl
|
|
278
|
+
diagram-rule uniform-spacing: Gaps between neighbouring boxes are even
|
|
279
|
+
category: space
|
|
280
|
+
evaluator: code
|
|
281
|
+
weight: 2
|
|
282
|
+
Uneven gaps falsely imply grouping by proximity.
|
|
283
|
+
Code: each box's gap to its nearest neighbour; 1 − min(1, coefficient of variation of those gaps).
|
|
284
|
+
Source: ELK and Graphviz node spacing; Davidson & Harel 1996.
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
```mdl
|
|
288
|
+
diagram-rule landscape-aspect: The drawing is landscape and fits a screen
|
|
289
|
+
category: space
|
|
290
|
+
evaluator: code
|
|
291
|
+
weight: 2
|
|
292
|
+
A tall strip or a long ribbon has to be scaled down to fit a page, shrinking its text.
|
|
293
|
+
Code: aspect ratio width / height scored 1 / (1 + |ln(ratio / 1.6)|).
|
|
294
|
+
Source: ELK aspectRatio (1.6); Purchase 2002 (width of layout).
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
```mdl
|
|
298
|
+
diagram-rule readable-at-page-width: Text stays legible when the drawing fits a page
|
|
299
|
+
category: labels
|
|
300
|
+
evaluator: code
|
|
301
|
+
weight: 3
|
|
302
|
+
Diagrams are read as images in documents and pull requests; a drawing that must shrink below 11
|
|
303
|
+
pixel text to fit 1200 pixels has failed however good its geometry.
|
|
304
|
+
Code: the smallest text size after scaling the drawing to 1200 units wide, over 11, capped at 1.
|
|
305
|
+
Source: C4 (diagrams should stand alone); Tufte (resolution).
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Labels
|
|
309
|
+
|
|
310
|
+
```mdl
|
|
311
|
+
diagram-rule labels-clear: Labels never overlap other labels, boxes or lines
|
|
312
|
+
category: labels
|
|
313
|
+
evaluator: both
|
|
314
|
+
weight: 6
|
|
315
|
+
question: Can every label be read on its own, without running into another label, a box or a line?
|
|
316
|
+
yes: Every label sits clear of other text and lines.
|
|
317
|
+
no: Some label overlaps another label, a box or a line, or is cut off.
|
|
318
|
+
Overlapping text is illegible; label placement treats non-overlap as a hard constraint.
|
|
319
|
+
Code: label overlaps counted by `Diagnostics` plus labels that overflow their boxes, scored 1 / (1 + count).
|
|
320
|
+
Source: Imhof 1975; Kakoulis & Tollis 1997.
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
```mdl
|
|
324
|
+
diagram-rule labels-attached: Each arrow label sits nearest its own arrow
|
|
325
|
+
category: labels
|
|
326
|
+
evaluator: both
|
|
327
|
+
weight: 3
|
|
328
|
+
question: Is it clear which arrow each arrow label belongs to?
|
|
329
|
+
yes: Every label sits on or right beside its own arrow.
|
|
330
|
+
no: Some label sits between arrows, or nearer another arrow than its own.
|
|
331
|
+
A label equally near two arrows is a misreading waiting to happen.
|
|
332
|
+
Code: the share of arrow labels whose nearest connector is their own.
|
|
333
|
+
Source: Imhof 1975; ELK edge-label placement.
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Overall
|
|
337
|
+
|
|
338
|
+
```mdl
|
|
339
|
+
diagram-rule consistent-notation: The same kind of thing always looks the same
|
|
340
|
+
category: hierarchy
|
|
341
|
+
evaluator: vision
|
|
342
|
+
weight: 2
|
|
343
|
+
question: Does each kind of element (box, group, arrow style) look the same everywhere, with no unexplained differences in size, colour or line style?
|
|
344
|
+
yes: Every kind of element has one consistent look.
|
|
345
|
+
no: Similar elements differ in size, colour or line style for no evident reason.
|
|
346
|
+
Size, colour and line style are read as meaning; inconsistent use invites false distinctions.
|
|
347
|
+
Source: Bertin; C4 checklist; Ware 2020 ch. 5.
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
```mdl
|
|
351
|
+
diagram-rule minimal-non-data-ink: Nothing is drawn that carries no information
|
|
352
|
+
category: hierarchy
|
|
353
|
+
evaluator: vision
|
|
354
|
+
weight: 2
|
|
355
|
+
question: Is the drawing free of decoration that carries no information, such as background grids, shadows, heavy borders or gradients, with group frames lighter than the boxes they hold?
|
|
356
|
+
yes: Every mark carries meaning; frames recede behind the components.
|
|
357
|
+
no: Background grids, heavy frames or decoration compete with the structure.
|
|
358
|
+
Non-data ink competes with structure; frames are context, so they should recede.
|
|
359
|
+
Source: Tufte (data-ink ratio, layering and separation).
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
```mdl
|
|
363
|
+
diagram-rule tidy-overall: The diagram looks deliberately drawn
|
|
364
|
+
category: hierarchy
|
|
365
|
+
evaluator: vision
|
|
366
|
+
weight: 4
|
|
367
|
+
question: Does this look like a tidy diagram a careful designer drew by hand, rather than an automatic layout?
|
|
368
|
+
yes: It looks deliberate: aligned, balanced, uncluttered, easy to scan.
|
|
369
|
+
no: It looks machine-made: ragged, cluttered, with odd gaps or detours.
|
|
370
|
+
The gap between human-drawn and algorithmic layouts is the gap this library exists to close.
|
|
371
|
+
Source: HOLA; Purchase, Pilcher & Plimmer 2012.
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
## Appendix
|
|
375
|
+
|
|
376
|
+
```mdl
|
|
377
|
+
ext diagram-rule
|
|
378
|
+
uri: org.dxos.mdl.diagram-rule@1.0
|
|
379
|
+
desc: A rule a diagram's drawing can be scored against.
|
|
380
|
+
inline-prose: true
|
|
381
|
+
fields:
|
|
382
|
+
category: crossings | flow | alignment | space | groups | labels | edges | hierarchy
|
|
383
|
+
evaluator: code | vision | both
|
|
384
|
+
weight: Number
|
|
385
|
+
question?: Prose
|
|
386
|
+
yes?: Prose
|
|
387
|
+
no?: Prose
|
|
388
|
+
```
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as DecisionModel from 'effect/ai/DecisionModel';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
import * as Layer from 'effect/Layer';
|
|
8
|
+
import { describe, test } from 'vitest';
|
|
9
|
+
|
|
10
|
+
import { IMAGE_RULES, RULES, judge } from './aesthetics.ts';
|
|
11
|
+
import { contentOf } from './architecture.ts';
|
|
12
|
+
import { parse } from './mermaid.ts';
|
|
13
|
+
import { evaluate } from './score.ts';
|
|
14
|
+
|
|
15
|
+
const GRAPH = parse('flowchart TB\n A --> B');
|
|
16
|
+
|
|
17
|
+
/** A model that answers every question with 0.7, recording the encoded input and questions it was asked. */
|
|
18
|
+
type Calls = { count: number; questions: string[]; state?: unknown; images?: readonly DecisionModel.Image[] };
|
|
19
|
+
|
|
20
|
+
const stubModel = (calls: Calls, supportsImages = false) =>
|
|
21
|
+
Layer.effect(
|
|
22
|
+
DecisionModel.DecisionModel,
|
|
23
|
+
DecisionModel.make({
|
|
24
|
+
supportsImages,
|
|
25
|
+
decide: ({ decisions, state, images }) =>
|
|
26
|
+
Effect.sync(() => {
|
|
27
|
+
calls.count += 1;
|
|
28
|
+
calls.questions = Object.keys(decisions);
|
|
29
|
+
calls.state = state;
|
|
30
|
+
calls.images = images;
|
|
31
|
+
return {
|
|
32
|
+
answers: Object.fromEntries(
|
|
33
|
+
Object.keys(decisions).map((key) => [key, { _tag: 'Probability' as const, probability: 0.7 }]),
|
|
34
|
+
),
|
|
35
|
+
usage: { inputTokens: undefined, outputTokens: undefined },
|
|
36
|
+
};
|
|
37
|
+
}),
|
|
38
|
+
}),
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
describe('aesthetics', () => {
|
|
42
|
+
test('rules are judged from the layout in one batched call', ({ expect }) => {
|
|
43
|
+
const calls: Calls = { count: 0, questions: [] };
|
|
44
|
+
const scores = Effect.runSync(
|
|
45
|
+
evaluate([judge()], { content: contentOf(GRAPH, { layout: 'row 1 (top): "A"' }) }).pipe(
|
|
46
|
+
Effect.provide(stubModel(calls)),
|
|
47
|
+
),
|
|
48
|
+
);
|
|
49
|
+
expect(calls.count).toBe(1);
|
|
50
|
+
expect(calls.state).toMatchObject({ layout: 'row 1 (top): "A"' });
|
|
51
|
+
expect(calls.questions).toEqual(RULES.map(({ key }) => key));
|
|
52
|
+
expect(scores.every(({ kind, score }) => kind === 'aesthetics' && score === 0.7)).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('without a layout nothing is asked and every rule is an error', ({ expect }) => {
|
|
56
|
+
const calls: Calls = { count: 0, questions: [] };
|
|
57
|
+
const scores = Effect.runSync(
|
|
58
|
+
evaluate([judge()], { content: contentOf(GRAPH) }).pipe(Effect.provide(stubModel(calls))),
|
|
59
|
+
);
|
|
60
|
+
expect(calls.count).toBe(0);
|
|
61
|
+
expect(scores.every(({ error }) => error === 'No layout to judge.')).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('an image of the page is judged in place of a layout, by a model that reads images', ({ expect }) => {
|
|
65
|
+
const calls: Calls = { count: 0, questions: [] };
|
|
66
|
+
const images = [{ mediaType: 'image/png', data: 'iVBORw==' }];
|
|
67
|
+
const scores = Effect.runSync(
|
|
68
|
+
evaluate([judge(IMAGE_RULES)], { content: contentOf(GRAPH), images }).pipe(
|
|
69
|
+
Effect.provide(stubModel(calls, true)),
|
|
70
|
+
),
|
|
71
|
+
);
|
|
72
|
+
expect(calls.count).toBe(1);
|
|
73
|
+
expect(calls.images).toEqual(images);
|
|
74
|
+
expect(IMAGE_RULES.every(({ instructions }) => instructions.includes('the attached image is the page'))).toBe(true);
|
|
75
|
+
expect(scores.every(({ score, error }) => !error && score === 0.7)).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('a model that reads no images scores every rule as an error rather than judging blind', ({ expect }) => {
|
|
79
|
+
const calls: Calls = { count: 0, questions: [] };
|
|
80
|
+
const scores = Effect.runSync(
|
|
81
|
+
evaluate([judge(IMAGE_RULES)], {
|
|
82
|
+
content: contentOf(GRAPH),
|
|
83
|
+
images: [{ mediaType: 'image/png', data: 'iVBORw==' }],
|
|
84
|
+
}).pipe(Effect.provide(stubModel(calls))),
|
|
85
|
+
);
|
|
86
|
+
expect(calls.count).toBe(0);
|
|
87
|
+
expect(scores.every(({ error }) => error !== undefined)).toBe(true);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
//
|
|
6
|
+
// Aesthetic rules: whether a diagram reads well as drawn, which the layout objective's counts
|
|
7
|
+
// (crossings, bends, length) do not capture — arrows against the flow, labels on top of boxes, edges
|
|
8
|
+
// merged into one trunk, an entry point in the far corner of its group. Judged from `Content.layout`,
|
|
9
|
+
// the text rendering of the page, by the same batched decision call as `Architecture`.
|
|
10
|
+
//
|
|
11
|
+
|
|
12
|
+
import type * as DecisionModel from 'effect/ai/DecisionModel';
|
|
13
|
+
import * as Effect from 'effect/Effect';
|
|
14
|
+
|
|
15
|
+
import * as Architecture from './architecture.ts';
|
|
16
|
+
import type * as Score from './score.ts';
|
|
17
|
+
|
|
18
|
+
const HOW_TO_READ =
|
|
19
|
+
'The input is a diagram as drawn: `layout` renders the page as text, a character drawing followed by a ' +
|
|
20
|
+
'row-by-row reading of the boxes and of which way each arrow runs. Judge how the drawing reads to a person ' +
|
|
21
|
+
'looking at it, not whether the architecture it shows is good.';
|
|
22
|
+
|
|
23
|
+
const HOW_TO_SEE =
|
|
24
|
+
'The input is a diagram as drawn: the attached image is the page. Judge how the drawing reads to a person ' +
|
|
25
|
+
'looking at it, not whether the architecture it shows is good.';
|
|
26
|
+
|
|
27
|
+
const rule = (
|
|
28
|
+
id: string,
|
|
29
|
+
key: string,
|
|
30
|
+
description: string,
|
|
31
|
+
question: string,
|
|
32
|
+
criteria: Architecture.Rule['criteria'],
|
|
33
|
+
): Architecture.Rule => ({ id, key, description, instructions: `${HOW_TO_READ} ${question}`, criteria });
|
|
34
|
+
|
|
35
|
+
export const RULES: readonly Architecture.Rule[] = [
|
|
36
|
+
rule(
|
|
37
|
+
'arrows-follow-flow',
|
|
38
|
+
'arrowsFollowFlow',
|
|
39
|
+
'Arrows run with the main direction of the diagram, inside groups as well as between them.',
|
|
40
|
+
'Do the arrows run with the main direction of the diagram (down in a top-to-bottom drawing, right in a ' +
|
|
41
|
+
'left-to-right one), inside groups as well as between them, with at most a few pointing back against it? ' +
|
|
42
|
+
'Arrows with a hollow triangle head (inheritance, implementation) point to the abstraction above by ' +
|
|
43
|
+
'convention and do not count against the flow.',
|
|
44
|
+
{
|
|
45
|
+
true: 'Nearly every arrow runs with the flow; a reader follows dependencies in one direction.',
|
|
46
|
+
false: 'Many arrows, or whole chains inside a group, run up or back against the flow.',
|
|
47
|
+
},
|
|
48
|
+
),
|
|
49
|
+
rule(
|
|
50
|
+
'readable-labels',
|
|
51
|
+
'readableLabels',
|
|
52
|
+
'Every box and arrow label can be read on its own.',
|
|
53
|
+
'Can every box label and arrow label be read on its own, without running into another label, a line or a box?',
|
|
54
|
+
{
|
|
55
|
+
true: 'Every label sits clear of other text and lines.',
|
|
56
|
+
false: 'Some label overlaps another label, a box or a line, or is cut off.',
|
|
57
|
+
},
|
|
58
|
+
),
|
|
59
|
+
rule(
|
|
60
|
+
'traceable-arrows',
|
|
61
|
+
'traceableArrows',
|
|
62
|
+
'Each arrow can be followed from its start to its end.',
|
|
63
|
+
'Can a reader follow each arrow from its start to its end without ambiguity — no two arrows sharing a line ' +
|
|
64
|
+
'segment or merged into one trunk, no arrow running through a group it does not connect to, no long detours ' +
|
|
65
|
+
'around the page, and few crossings?',
|
|
66
|
+
{
|
|
67
|
+
true: 'Each arrow has its own short, direct route.',
|
|
68
|
+
false: 'Arrows share trunks, detour far, or cross so that their ends are hard to pair up.',
|
|
69
|
+
},
|
|
70
|
+
),
|
|
71
|
+
rule(
|
|
72
|
+
'entries-face-callers',
|
|
73
|
+
'entriesFaceCallers',
|
|
74
|
+
'Boxes that outside arrows reach sit on the side of their group facing the callers.',
|
|
75
|
+
'Within each group, do the boxes that arrows from outside the group reach sit on the side of the group ' +
|
|
76
|
+
'facing those callers, so that cross-group arrows are short and do not run through or around the group?',
|
|
77
|
+
{
|
|
78
|
+
true: 'Cross-group arrows land on the near side of each group.',
|
|
79
|
+
false: 'An arrow from outside travels through or around a group to reach a box on its far side.',
|
|
80
|
+
},
|
|
81
|
+
),
|
|
82
|
+
rule(
|
|
83
|
+
'compact',
|
|
84
|
+
'compact',
|
|
85
|
+
'The drawing is packed tightly: no large empty regions, groups sized to their content.',
|
|
86
|
+
'Is the drawing compact — no large empty regions between or inside groups, groups no bigger than what they ' +
|
|
87
|
+
'hold, and boxes that are connected placed next to each other?',
|
|
88
|
+
{
|
|
89
|
+
true: 'The page is tightly packed; connected boxes are neighbours.',
|
|
90
|
+
false: 'There are large empty regions, stretched groups, or connected boxes far apart.',
|
|
91
|
+
},
|
|
92
|
+
),
|
|
93
|
+
rule(
|
|
94
|
+
'groups-aligned',
|
|
95
|
+
'groupsAligned',
|
|
96
|
+
'Groups line up with each other rather than floating at arbitrary offsets.',
|
|
97
|
+
'Do the groups line up with one another — sharing a top edge or a left edge where they sit side by side or ' +
|
|
98
|
+
'stacked — rather than one group floating at an arbitrary height or offset beside another?',
|
|
99
|
+
{
|
|
100
|
+
true: 'Groups share edges and read as one arrangement.',
|
|
101
|
+
false: 'A group sits at an arbitrary offset from its neighbours.',
|
|
102
|
+
},
|
|
103
|
+
),
|
|
104
|
+
rule(
|
|
105
|
+
'names-only',
|
|
106
|
+
'namesOnly',
|
|
107
|
+
'Each box is labelled with a component name alone, without comments or qualifiers.',
|
|
108
|
+
'Is every box labelled with just the name of what it is, with no comments, limits or qualifiers mixed into the ' +
|
|
109
|
+
'name (such as "no cap", "≤10" or a colon followed by a description)?',
|
|
110
|
+
{
|
|
111
|
+
true: 'Box labels are plain names; any commentary sits on arrows or not at all.',
|
|
112
|
+
false: 'Some box label mixes a name with a comment, a limit or a description.',
|
|
113
|
+
},
|
|
114
|
+
),
|
|
115
|
+
rule(
|
|
116
|
+
'aligned-grid',
|
|
117
|
+
'alignedGrid',
|
|
118
|
+
'Boxes line up in rows and columns with consistent spacing.',
|
|
119
|
+
'Are the boxes aligned into clear rows and columns with consistent spacing, so the structure reads as a grid?',
|
|
120
|
+
{
|
|
121
|
+
true: 'Boxes share rows and columns with even gaps.',
|
|
122
|
+
false: 'Boxes are staggered or unevenly spaced, with no clear rows or columns.',
|
|
123
|
+
},
|
|
124
|
+
),
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
/** {@link RULES} for a judge shown the page as an image (`Architecture.Subject.images`) instead of `layout`. */
|
|
128
|
+
export const IMAGE_RULES: readonly Architecture.Rule[] = RULES.map((rule) => ({
|
|
129
|
+
...rule,
|
|
130
|
+
instructions: `${HOW_TO_SEE}${rule.instructions.slice(HOW_TO_READ.length)}`,
|
|
131
|
+
}));
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Every aesthetic rule as a score from one `DecisionModel` call. Without `layout` or an image of the
|
|
135
|
+
* page there is nothing to judge, so each rule reports an error rather than a guess.
|
|
136
|
+
*/
|
|
137
|
+
export const judge = (
|
|
138
|
+
rules: readonly Architecture.Rule[] = RULES,
|
|
139
|
+
): Score.Batch<Architecture.Subject, DecisionModel.DecisionModel> => {
|
|
140
|
+
const inner = Architecture.judge(rules, 'aesthetics');
|
|
141
|
+
return {
|
|
142
|
+
entries: inner.entries,
|
|
143
|
+
evaluate: (subject) =>
|
|
144
|
+
subject.content.layout || subject.images?.length
|
|
145
|
+
? inner.evaluate(subject)
|
|
146
|
+
: Effect.succeed(rules.map(() => ({ score: 0, error: 'No layout to judge.' }))),
|
|
147
|
+
};
|
|
148
|
+
};
|