scrum-skills 0.3.2 → 0.3.4
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/.agents/skills/diagram-design/LICENSE +4 -0
- package/.agents/skills/diagram-design/SKILL.md +423 -0
- package/.agents/skills/diagram-design/UPSTREAM.json +88 -0
- package/.agents/skills/diagram-design/UPSTREAM.md +23 -0
- package/.agents/skills/diagram-design/assets/example-architecture-containment.html +205 -0
- package/.agents/skills/diagram-design/assets/example-architecture-delta.html +193 -0
- package/.agents/skills/diagram-design/assets/example-architecture.html +183 -0
- package/.agents/skills/diagram-design/assets/example-axonometric-plan-campus.html +108 -0
- package/.agents/skills/diagram-design/assets/example-axonometric-plan-coffee-shop.html +121 -0
- package/.agents/skills/diagram-design/assets/example-axonometric-plan-warehouse.html +127 -0
- package/.agents/skills/diagram-design/assets/example-axonometric-plan.html +114 -0
- package/.agents/skills/diagram-design/assets/example-bar.html +129 -0
- package/.agents/skills/diagram-design/assets/example-beeswarm.html +384 -0
- package/.agents/skills/diagram-design/assets/example-box-plot.html +127 -0
- package/.agents/skills/diagram-design/assets/example-bubble.html +147 -0
- package/.agents/skills/diagram-design/assets/example-bump.html +142 -0
- package/.agents/skills/diagram-design/assets/example-data-flow.html +143 -0
- package/.agents/skills/diagram-design/assets/example-datalake.html +269 -0
- package/.agents/skills/diagram-design/assets/example-db-schema.html +302 -0
- package/.agents/skills/diagram-design/assets/example-dependency.html +224 -0
- package/.agents/skills/diagram-design/assets/example-deployment.html +190 -0
- package/.agents/skills/diagram-design/assets/example-dp-integration.html +81 -0
- package/.agents/skills/diagram-design/assets/example-dp-security-matrix.html +88 -0
- package/.agents/skills/diagram-design/assets/example-er.html +202 -0
- package/.agents/skills/diagram-design/assets/example-exploded-ai-stack.html +85 -0
- package/.agents/skills/diagram-design/assets/example-exploded-keyboard.html +84 -0
- package/.agents/skills/diagram-design/assets/example-exploded-phone.html +83 -0
- package/.agents/skills/diagram-design/assets/example-exploded-unboxing.html +84 -0
- package/.agents/skills/diagram-design/assets/example-exploded.html +83 -0
- package/.agents/skills/diagram-design/assets/example-fishbone.html +152 -0
- package/.agents/skills/diagram-design/assets/example-flowchart.html +163 -0
- package/.agents/skills/diagram-design/assets/example-gantt.html +137 -0
- package/.agents/skills/diagram-design/assets/example-heatmap.html +224 -0
- package/.agents/skills/diagram-design/assets/example-high-level-vertical.html +325 -0
- package/.agents/skills/diagram-design/assets/example-high-level.html +301 -0
- package/.agents/skills/diagram-design/assets/example-import-drawio.html +185 -0
- package/.agents/skills/diagram-design/assets/example-import-excalidraw.html +70 -0
- package/.agents/skills/diagram-design/assets/example-import-mermaid.html +70 -0
- package/.agents/skills/diagram-design/assets/example-it-state.html +128 -0
- package/.agents/skills/diagram-design/assets/example-journey.html +148 -0
- package/.agents/skills/diagram-design/assets/example-kanban.html +174 -0
- package/.agents/skills/diagram-design/assets/example-layers.html +123 -0
- package/.agents/skills/diagram-design/assets/example-line.html +111 -0
- package/.agents/skills/diagram-design/assets/example-loop.html +100 -0
- package/.agents/skills/diagram-design/assets/example-marimekko.html +148 -0
- package/.agents/skills/diagram-design/assets/example-medallion.html +159 -0
- package/.agents/skills/diagram-design/assets/example-nested.html +136 -0
- package/.agents/skills/diagram-design/assets/example-org-chart.html +45 -0
- package/.agents/skills/diagram-design/assets/example-polar.html +116 -0
- package/.agents/skills/diagram-design/assets/example-process.html +64 -0
- package/.agents/skills/diagram-design/assets/example-pyramid.html +119 -0
- package/.agents/skills/diagram-design/assets/example-quadrant-consultant.html +166 -0
- package/.agents/skills/diagram-design/assets/example-quadrant.html +135 -0
- package/.agents/skills/diagram-design/assets/example-radar.html +139 -0
- package/.agents/skills/diagram-design/assets/example-ridgeline.html +104 -0
- package/.agents/skills/diagram-design/assets/example-sankey.html +150 -0
- package/.agents/skills/diagram-design/assets/example-scatter.html +141 -0
- package/.agents/skills/diagram-design/assets/example-sequence-oauth.html +227 -0
- package/.agents/skills/diagram-design/assets/example-sequence.html +222 -0
- package/.agents/skills/diagram-design/assets/example-service-blueprint.html +185 -0
- package/.agents/skills/diagram-design/assets/example-slopegraph.html +119 -0
- package/.agents/skills/diagram-design/assets/example-state-lifecycle.html +92 -0
- package/.agents/skills/diagram-design/assets/example-state.html +147 -0
- package/.agents/skills/diagram-design/assets/example-story-map.html +184 -0
- package/.agents/skills/diagram-design/assets/example-streamgraph.html +99 -0
- package/.agents/skills/diagram-design/assets/example-swimlane.html +172 -0
- package/.agents/skills/diagram-design/assets/example-timeline.html +135 -0
- package/.agents/skills/diagram-design/assets/example-tree-block-decomposition.html +151 -0
- package/.agents/skills/diagram-design/assets/example-tree.html +173 -0
- package/.agents/skills/diagram-design/assets/example-treemap.html +107 -0
- package/.agents/skills/diagram-design/assets/example-uml-class.html +188 -0
- package/.agents/skills/diagram-design/assets/example-venn.html +110 -0
- package/.agents/skills/diagram-design/assets/example-wardley.html +153 -0
- package/.agents/skills/diagram-design/assets/example-waterfall.html +129 -0
- package/.agents/skills/diagram-design/assets/template-dark.html +99 -0
- package/.agents/skills/diagram-design/assets/template-full.html +421 -0
- package/.agents/skills/diagram-design/assets/template-motion.html +448 -0
- package/.agents/skills/diagram-design/assets/template-terminal.html +188 -0
- package/.agents/skills/diagram-design/assets/template.html +100 -0
- package/.agents/skills/diagram-design/references/animation.md +143 -0
- package/.agents/skills/diagram-design/references/export-registry.md +78 -0
- package/.agents/skills/diagram-design/references/export.md +376 -0
- package/.agents/skills/diagram-design/references/import-drawio.md +181 -0
- package/.agents/skills/diagram-design/references/import-excalidraw.md +130 -0
- package/.agents/skills/diagram-design/references/import-mermaid.md +137 -0
- package/.agents/skills/diagram-design/references/layout-budget.md +97 -0
- package/.agents/skills/diagram-design/references/onboarding.md +368 -0
- package/.agents/skills/diagram-design/references/output-spec.md +277 -0
- package/.agents/skills/diagram-design/references/primitive-annotation.md +36 -0
- package/.agents/skills/diagram-design/references/primitive-icons.md +827 -0
- package/.agents/skills/diagram-design/references/primitive-sketchy.md +43 -0
- package/.agents/skills/diagram-design/references/primitive-terminal.md +76 -0
- package/.agents/skills/diagram-design/references/primitives-core.md +150 -0
- package/.agents/skills/diagram-design/references/profiles.md +183 -0
- package/.agents/skills/diagram-design/references/semantic-patterns.md +154 -0
- package/.agents/skills/diagram-design/references/style-guide.md +236 -0
- package/.agents/skills/diagram-design/references/style-vivid.md +148 -0
- package/.agents/skills/diagram-design/references/type-architecture-delta.md +143 -0
- package/.agents/skills/diagram-design/references/type-architecture.md +117 -0
- package/.agents/skills/diagram-design/references/type-axonometric-plan.md +83 -0
- package/.agents/skills/diagram-design/references/type-bar.md +108 -0
- package/.agents/skills/diagram-design/references/type-data-flow.md +371 -0
- package/.agents/skills/diagram-design/references/type-db-schema.md +48 -0
- package/.agents/skills/diagram-design/references/type-dependency.md +44 -0
- package/.agents/skills/diagram-design/references/type-deployment.md +45 -0
- package/.agents/skills/diagram-design/references/type-dp-integration.md +407 -0
- package/.agents/skills/diagram-design/references/type-dp-security-matrix.md +379 -0
- package/.agents/skills/diagram-design/references/type-er.md +25 -0
- package/.agents/skills/diagram-design/references/type-exploded.md +115 -0
- package/.agents/skills/diagram-design/references/type-fishbone.md +75 -0
- package/.agents/skills/diagram-design/references/type-flowchart.md +23 -0
- package/.agents/skills/diagram-design/references/type-gantt.md +45 -0
- package/.agents/skills/diagram-design/references/type-heatmap.md +80 -0
- package/.agents/skills/diagram-design/references/type-high-level.md +470 -0
- package/.agents/skills/diagram-design/references/type-it-state.md +468 -0
- package/.agents/skills/diagram-design/references/type-journey.md +55 -0
- package/.agents/skills/diagram-design/references/type-kanban.md +49 -0
- package/.agents/skills/diagram-design/references/type-layers.md +26 -0
- package/.agents/skills/diagram-design/references/type-line.md +337 -0
- package/.agents/skills/diagram-design/references/type-loop.md +223 -0
- package/.agents/skills/diagram-design/references/type-medallion.md +356 -0
- package/.agents/skills/diagram-design/references/type-nested.md +22 -0
- package/.agents/skills/diagram-design/references/type-org-chart.md +44 -0
- package/.agents/skills/diagram-design/references/type-polar.md +136 -0
- package/.agents/skills/diagram-design/references/type-process.md +490 -0
- package/.agents/skills/diagram-design/references/type-pyramid.md +33 -0
- package/.agents/skills/diagram-design/references/type-quadrant.md +81 -0
- package/.agents/skills/diagram-design/references/type-radar.md +82 -0
- package/.agents/skills/diagram-design/references/type-sankey.md +75 -0
- package/.agents/skills/diagram-design/references/type-scatter.md +276 -0
- package/.agents/skills/diagram-design/references/type-sequence.md +130 -0
- package/.agents/skills/diagram-design/references/type-service-blueprint.md +103 -0
- package/.agents/skills/diagram-design/references/type-state.md +30 -0
- package/.agents/skills/diagram-design/references/type-story-map.md +46 -0
- package/.agents/skills/diagram-design/references/type-swimlane.md +22 -0
- package/.agents/skills/diagram-design/references/type-timeline.md +20 -0
- package/.agents/skills/diagram-design/references/type-tree.md +24 -0
- package/.agents/skills/diagram-design/references/type-treemap.md +137 -0
- package/.agents/skills/diagram-design/references/type-uml-class.md +65 -0
- package/.agents/skills/diagram-design/references/type-venn.md +26 -0
- package/.agents/skills/diagram-design/references/type-wardley.md +39 -0
- package/.agents/skills/diagram-design/references/type-waterfall.md +75 -0
- package/.agents/skills/scrum-meta/SKILL.md +36 -36
- package/.agents/skills/scrum-planning/SKILL.md +61 -61
- package/.agents/skills/scrum-planning/references/schema.md +384 -336
- package/.agents/skills/scrum-report/SKILL.md +42 -42
- package/.agents/skills/scrum-sprint/SKILL.md +49 -49
- package/.agents/skills/scrum-tdd/SKILL.md +140 -127
- package/README.md +24 -1
- package/assets/board/200.html +1 -1
- package/assets/board/404.html +1 -1
- package/assets/board/_nuxt/4gQF_HS3.js +1 -0
- package/assets/board/_nuxt/5okfNy5D.js +1 -0
- package/assets/board/_nuxt/{Bb3Limos.js → B0C2jsdk.js} +1 -1
- package/assets/board/_nuxt/B1j6UC4U.js +1 -0
- package/assets/board/_nuxt/B3LKy6o2.js +1 -0
- package/assets/board/_nuxt/BAQQngAq.js +13 -0
- package/assets/board/_nuxt/BE6t7Q2m.js +1 -0
- package/assets/board/_nuxt/BGwhkDRl.js +1 -0
- package/assets/board/_nuxt/BLvbupFS.js +1 -0
- package/assets/board/_nuxt/BQgzP7ur.js +1 -0
- package/assets/board/_nuxt/BRZIF_wo.js +1 -0
- package/assets/board/_nuxt/BU8vdZUs.js +95 -0
- package/assets/board/_nuxt/BW3xJdEx.js +1 -0
- package/assets/board/_nuxt/{Cm9lkUvP.js → BY36DFyj.js} +1 -1
- package/assets/board/_nuxt/BZlZD94i.js +1 -0
- package/assets/board/_nuxt/BZvXNwev.js +3 -0
- package/assets/board/_nuxt/BiWrMOsL.js +1 -0
- package/assets/board/_nuxt/BkoKCNUY.js +1 -0
- package/assets/board/_nuxt/BlKmZhhF.js +1 -0
- package/assets/board/_nuxt/BmBpmU73.js +1 -0
- package/assets/board/_nuxt/BmrSh0rL.js +1 -0
- package/assets/board/_nuxt/BrWE0up6.js +1 -0
- package/assets/board/_nuxt/Bs52qkgk.js +1 -0
- package/assets/board/_nuxt/BuBzMkFZ.js +1 -0
- package/assets/board/_nuxt/Bubk0xn-.js +1 -0
- package/assets/board/_nuxt/BxuBMtk_.js +3 -0
- package/assets/board/_nuxt/By1KXXLR.js +1 -0
- package/assets/board/_nuxt/BygIGL69.js +2 -0
- package/assets/board/_nuxt/C-fx-vFO.js +1 -0
- package/assets/board/_nuxt/C1iO5KH3.js +1 -0
- package/assets/board/_nuxt/C2YPmTzt.js +1 -0
- package/assets/board/_nuxt/C3SpAhCW.js +1 -0
- package/assets/board/_nuxt/C3ZRWz5L.js +1 -0
- package/assets/board/_nuxt/C4WdPP6K.js +1 -0
- package/assets/board/_nuxt/C6o03JHe.js +1 -0
- package/assets/board/_nuxt/C8qB5llI.js +1 -0
- package/assets/board/_nuxt/C9MgRdhu.js +1 -0
- package/assets/board/_nuxt/CCA81-4P.js +1 -0
- package/assets/board/_nuxt/{D2O8Q8x9.js → CFCM18fF.js} +1 -1
- package/assets/board/_nuxt/CGXljzd_.js +1 -0
- package/assets/board/_nuxt/CI0scS-N.js +1 -0
- package/assets/board/_nuxt/CJs9B42o.js +1 -0
- package/assets/board/_nuxt/COmtTtfB.js +1 -0
- package/assets/board/_nuxt/CPYm4J_K.js +1 -0
- package/assets/board/_nuxt/CXG7GZes.js +1 -0
- package/assets/board/_nuxt/CYGiZn-X.js +1 -0
- package/assets/board/_nuxt/CaE8VlUp.js +1 -0
- package/assets/board/_nuxt/Cajxc4Dw.js +1 -0
- package/assets/board/_nuxt/CcGiNUgA.js +1 -0
- package/assets/board/_nuxt/Ccgv3lDj.js +1 -0
- package/assets/board/_nuxt/CdPGFZyy.js +1 -0
- package/assets/board/_nuxt/{DfR3IZN_.js → Cdf_T8Xa.js} +1 -1
- package/assets/board/_nuxt/CgQ30j_6.js +1 -0
- package/assets/board/_nuxt/Ck0-FqVI.js +1 -0
- package/assets/board/_nuxt/CkWKLtjO.js +1 -0
- package/assets/board/_nuxt/CoipdpDu.js +1 -0
- package/assets/board/_nuxt/Cq7hsCKB.js +1 -0
- package/assets/board/_nuxt/D166WVUG.js +1 -0
- package/assets/board/_nuxt/D1V1gysQ.js +1 -0
- package/assets/board/_nuxt/D4Dpzo9e.js +1 -0
- package/assets/board/_nuxt/D5i92OZO.js +1 -0
- package/assets/board/_nuxt/D6dUp4Nm.js +9 -0
- package/assets/board/_nuxt/DFQDFspC.js +1 -0
- package/assets/board/_nuxt/DHX2tw9J.js +1 -0
- package/assets/board/_nuxt/DJM7u8P3.js +1 -0
- package/assets/board/_nuxt/{BPTrRhcw.js → DLr85ebo.js} +1 -1
- package/assets/board/_nuxt/DOMM1wfl.js +1 -0
- package/assets/board/_nuxt/DU6ArlUN.js +1 -0
- package/assets/board/_nuxt/{B_SlFcWp.js → DUgGsde6.js} +1 -1
- package/assets/board/_nuxt/DV4ti-Pq.js +1 -0
- package/assets/board/_nuxt/D_ct-Stz.js +2 -0
- package/assets/board/_nuxt/DbWAComO.js +1 -0
- package/assets/board/_nuxt/DbwlcHAt.js +1 -0
- package/assets/board/_nuxt/Dki9VkAL.js +3 -0
- package/assets/board/_nuxt/DlNJhOoc.js +1 -0
- package/assets/board/_nuxt/Dmg78zOY.js +3 -0
- package/assets/board/_nuxt/DqG7CtD4.js +1 -0
- package/assets/board/_nuxt/DqUSsCJ2.js +1 -0
- package/assets/board/_nuxt/Du2F4WXh.js +1 -0
- package/assets/board/_nuxt/DuRiEXn4.js +1 -0
- package/assets/board/_nuxt/DwCE3305.js +1 -0
- package/assets/board/_nuxt/DwzGxoX2.js +1 -0
- package/assets/board/_nuxt/DxBwVNyP.js +2 -0
- package/assets/board/_nuxt/DzmVW5-D.js +1 -0
- package/assets/board/_nuxt/I-sGtDIB.js +6 -0
- package/assets/board/_nuxt/JU9-x68N.js +1 -0
- package/assets/board/_nuxt/Lozm7hhE.js +1 -0
- package/assets/board/_nuxt/MMsTMkcB.js +1 -0
- package/assets/board/_nuxt/MS7CGmCB.js +1 -0
- package/assets/board/_nuxt/OKKaKILz.js +1 -0
- package/assets/board/_nuxt/{Csqgy925.js → P6qlvBHA.js} +1 -1
- package/assets/board/_nuxt/SrNUvNwO.js +1 -0
- package/assets/board/_nuxt/{DSaWoFHS.js → SywdkA98.js} +1 -1
- package/assets/board/_nuxt/TBIRpVLG.js +1 -0
- package/assets/board/_nuxt/TlTrMIMm.js +1 -0
- package/assets/board/_nuxt/Utb4TVOl.js +1 -0
- package/assets/board/_nuxt/Zv9-SOQF.js +1 -0
- package/assets/board/_nuxt/_id_.B6iqDPxe.css +1 -0
- package/assets/board/_nuxt/b7xtxFRl.js +1 -0
- package/assets/board/_nuxt/builds/latest.json +1 -1
- package/assets/board/_nuxt/builds/meta/08bebe14-0f1b-4d2a-8de4-e84e122d10e9.json +1 -0
- package/assets/board/_nuxt/diagrams.pyhqNtmr.css +1 -0
- package/assets/board/_nuxt/{entry.CKHePurH.css → entry.BBv-sBnP.css} +1 -1
- package/assets/board/_nuxt/error-404.CgJNdpB5.css +1 -0
- package/assets/board/_nuxt/error-500.WYSoH44y.css +1 -0
- package/assets/board/_nuxt/jbri6q1p.js +1 -0
- package/assets/board/_nuxt/q_8bxRMt.js +1 -0
- package/assets/board/burndown/index.html +1 -1
- package/assets/board/diagrams/index.html +1 -0
- package/assets/board/epics/index.html +1 -1
- package/assets/board/gantt/index.html +1 -1
- package/assets/board/index.html +1 -1
- package/assets/board/last-change/index.html +1 -1
- package/assets/board/milestones/index.html +1 -1
- package/assets/board/tests/index.html +1 -1
- package/bin/scrum-skills.mjs +13 -0
- package/package.json +2 -1
- package/templates/AGENTS.md +10 -0
- package/tools/diagram-import.mjs +238 -0
- package/tools/diagram-lint.mjs +518 -0
- package/tools/diagram.mjs +299 -0
- package/tools/last-change.mjs +326 -326
- package/tools/projects.mjs +29 -4
- package/tools/serve.mjs +27 -1
- package/tools/validate.mjs +781 -664
- package/assets/board/_nuxt/1eqboJxR.js +0 -1
- package/assets/board/_nuxt/2K7ag6ES.js +0 -1
- package/assets/board/_nuxt/51OWHj8c.js +0 -2
- package/assets/board/_nuxt/6gFtTBwm.js +0 -1
- package/assets/board/_nuxt/B3jkABw9.js +0 -1
- package/assets/board/_nuxt/B5n2_jZN.js +0 -1
- package/assets/board/_nuxt/BDK2Hj7B.js +0 -1
- package/assets/board/_nuxt/BDcnzXHq.js +0 -1
- package/assets/board/_nuxt/BaG8y0Wt.js +0 -1
- package/assets/board/_nuxt/BbwJq5pb.js +0 -1
- package/assets/board/_nuxt/BgLeKwx0.js +0 -1
- package/assets/board/_nuxt/Bi9Axj3V.js +0 -1
- package/assets/board/_nuxt/BmxvtaMq.js +0 -1
- package/assets/board/_nuxt/BodFvto3.js +0 -1
- package/assets/board/_nuxt/BpWeHLuN.js +0 -1
- package/assets/board/_nuxt/BuOKDFiJ.js +0 -1
- package/assets/board/_nuxt/C40iTe-x.js +0 -1
- package/assets/board/_nuxt/C7K0R7F-.js +0 -2
- package/assets/board/_nuxt/C8vL692X.js +0 -1
- package/assets/board/_nuxt/C9StZL3D.js +0 -3
- package/assets/board/_nuxt/CGI_ZV79.js +0 -1
- package/assets/board/_nuxt/CJ2mz0WY.js +0 -1
- package/assets/board/_nuxt/CJMBJQuf.js +0 -1
- package/assets/board/_nuxt/CK-lOYG9.js +0 -1
- package/assets/board/_nuxt/CK9_veoz.js +0 -1
- package/assets/board/_nuxt/CLBcBrYf.js +0 -1
- package/assets/board/_nuxt/CMUi2-Lj.js +0 -1
- package/assets/board/_nuxt/COf6E6h4.js +0 -1
- package/assets/board/_nuxt/CPCUMJnF.js +0 -1
- package/assets/board/_nuxt/CPcKlmSm.js +0 -1
- package/assets/board/_nuxt/CQTvWh4i.js +0 -1
- package/assets/board/_nuxt/CSEEUF8S.js +0 -1
- package/assets/board/_nuxt/CSSb154I.js +0 -13
- package/assets/board/_nuxt/CgDTd7jS.js +0 -1
- package/assets/board/_nuxt/ChG76uh9.js +0 -1
- package/assets/board/_nuxt/Ci0qbkgh.js +0 -1
- package/assets/board/_nuxt/CqoQ__I_.js +0 -1
- package/assets/board/_nuxt/CuJUHLl8.js +0 -1
- package/assets/board/_nuxt/CvLiA4_O.js +0 -1
- package/assets/board/_nuxt/Cvs12oA0.js +0 -3
- package/assets/board/_nuxt/CyEXFe3V.js +0 -1
- package/assets/board/_nuxt/Cz6rZcLK.js +0 -1
- package/assets/board/_nuxt/D2FhHl1o.js +0 -1
- package/assets/board/_nuxt/D3YCAmfu.js +0 -1
- package/assets/board/_nuxt/D5CE81jK.js +0 -1
- package/assets/board/_nuxt/D75vbfSb.js +0 -1
- package/assets/board/_nuxt/D99LbGfb.js +0 -9
- package/assets/board/_nuxt/DEfLc7p3.js +0 -6
- package/assets/board/_nuxt/DF-nsa1Z.js +0 -1
- package/assets/board/_nuxt/DIc8yx98.js +0 -1
- package/assets/board/_nuxt/DLkAKotw.js +0 -1
- package/assets/board/_nuxt/DRPlYvb3.js +0 -1
- package/assets/board/_nuxt/DRlQgul3.js +0 -1
- package/assets/board/_nuxt/DSLbmoWa.js +0 -1
- package/assets/board/_nuxt/DWO9c9yp.js +0 -1
- package/assets/board/_nuxt/DZo3dYLJ.js +0 -3
- package/assets/board/_nuxt/D_S6N7_b.js +0 -1
- package/assets/board/_nuxt/DajA-Tkt.js +0 -1
- package/assets/board/_nuxt/DbHiyab-.js +0 -1
- package/assets/board/_nuxt/Dc7Ql6eE.js +0 -1
- package/assets/board/_nuxt/Dd9ZukPU.js +0 -1
- package/assets/board/_nuxt/Dg2LiqmN.js +0 -1
- package/assets/board/_nuxt/DhuleNnm.js +0 -1
- package/assets/board/_nuxt/DkI1Qxpp.js +0 -95
- package/assets/board/_nuxt/DnDHGSYI.js +0 -1
- package/assets/board/_nuxt/DpXJ_tCU.js +0 -1
- package/assets/board/_nuxt/DqZgF3hU.js +0 -1
- package/assets/board/_nuxt/DqcxvfbD.js +0 -1
- package/assets/board/_nuxt/DsA2lVci.js +0 -1
- package/assets/board/_nuxt/Dtsa-1NS.js +0 -1
- package/assets/board/_nuxt/Du91iq0p.js +0 -1
- package/assets/board/_nuxt/Dx1wcYFo.js +0 -1
- package/assets/board/_nuxt/EYTShbt4.js +0 -1
- package/assets/board/_nuxt/MaXJprwN.js +0 -1
- package/assets/board/_nuxt/P9x67aq2.js +0 -1
- package/assets/board/_nuxt/S08nFESZ.js +0 -3
- package/assets/board/_nuxt/V0uynWE2.js +0 -1
- package/assets/board/_nuxt/Vo4wMbnS.js +0 -1
- package/assets/board/_nuxt/ZVTqot-_.js +0 -1
- package/assets/board/_nuxt/ZngEd3HB.js +0 -1
- package/assets/board/_nuxt/_id_.B2NDKSlX.css +0 -1
- package/assets/board/_nuxt/assMu_Wn.js +0 -1
- package/assets/board/_nuxt/builds/meta/8e23aec9-5b67-4569-b8bf-d4f07e50ffdf.json +0 -1
- package/assets/board/_nuxt/error-404.BFmXOnCT.css +0 -1
- package/assets/board/_nuxt/error-500.BZ-DdjQ6.css +0 -1
- package/assets/board/_nuxt/rlePnzpc.js +0 -1
- package/assets/board/_nuxt/uKzCKCTq.js +0 -1
- package/assets/board/_nuxt/vwxaNGfc.js +0 -1
- package/assets/board/_nuxt/yh6sf_D5.js +0 -1
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Radar / Spider
|
|
2
|
+
|
|
3
|
+
**Best for:** comparing 3–5 entities across 3–5 quantitative criteria on a single normalized 0–N scale. Capability matrices, product or backend evaluations, framework/team scorecards. Where a comparison table starts running out of horizontal room, radar makes the shape of each option legible at a glance.
|
|
4
|
+
|
|
5
|
+
## Layout conventions
|
|
6
|
+
|
|
7
|
+
- **N axes (3–5).** Equally spaced on a regular polygon-N. First axis at the top (`-90°`), going clockwise. **Above 5 → split or use a comparison table.**
|
|
8
|
+
- **Five concentric grid rings** at fractions `0.2 / 0.4 / 0.6 / 0.8 / 1.0` of the radius. Drawn as closed polygons connecting the axis vertices at that fraction. Inner four at `rule` 0.10 opacity, outer ring at `rule-solid` 0.20 (a hint stronger to anchor the chart).
|
|
9
|
+
- **Axis spokes** from center to each outer vertex. `rule-solid` 0.20 opacity. **No arrowheads.**
|
|
10
|
+
- **Axis labels:** one word per spoke (Jobs-minimal). `node-name` role 11px weight 600. Place 16px outside the outer ring along the axis vector. Top/bottom = `text-anchor="middle"`; right side = `start`; left side = `end`.
|
|
11
|
+
- **Scale ticks** (e.g. `2 4 6 8 10`) only on the **first (top) axis** — putting numbers on every spoke clutters the chart fast. `arrow-label` role 8px, `muted`, anchored end at `cx − 6`.
|
|
12
|
+
- **Series polygon:** stroke 1.5px at the series color, fill the same color at `0.18` opacity (`0.22` in dark). Stroke 1.8px on the focal series — a subtle weight bump.
|
|
13
|
+
- **Vertex dots:** **only on the focal series**, `r=4` filled with the series color. Non-focal series are stroke-and-fill only. This is the load-bearing rule that keeps the chart readable at 4–5 series.
|
|
14
|
+
- **Drawing order:** dots-pattern bg → grid rings → axis spokes → axis labels → scale ticks → non-focal series (smallest area first) → focal series → focal vertex dots → legend.
|
|
15
|
+
- **Legend:** horizontal strip at the bottom (per the global rule). Swatch is a 16×8 rectangle (matches the polygon stroke+fill, not a circle), then the entity name. ~140px between entries. Optional italic tail on the right with the rationale (`"One coral. Position is the signal — color reserved for the recommended option."`).
|
|
16
|
+
|
|
17
|
+
## Math
|
|
18
|
+
|
|
19
|
+
For axis `i` (0-indexed) of `N`, value `v` on scale `S`, center `(cx, cy)`, outer radius `R`:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
angle = -π/2 + 2π · i / N
|
|
23
|
+
x = cx + (v / S) · R · cos(angle)
|
|
24
|
+
y = cy + (v / S) · R · sin(angle)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
A series with values `[v0, v1, ..., v(N-1)]` becomes a `<polygon>` with `points="x0,y0 x1,y1 ..."`.
|
|
28
|
+
|
|
29
|
+
### Pre-computed reference (N=5, cx=500, cy=240, R=160, S=10, integer-rounded)
|
|
30
|
+
|
|
31
|
+
| Fraction `f` | i=0 (top) | i=1 | i=2 | i=3 | i=4 |
|
|
32
|
+
|---|---|---|---|---|---|
|
|
33
|
+
| 0.2 | 500,208 | 530,230 | 519,266 | 481,266 | 470,230 |
|
|
34
|
+
| 0.4 | 500,176 | 561,220 | 538,292 | 462,292 | 439,220 |
|
|
35
|
+
| 0.6 | 500,144 | 591,211 | 556,317 | 444,317 | 409,211 |
|
|
36
|
+
| 0.8 | 500,112 | 622,201 | 575,343 | 425,343 | 378,201 |
|
|
37
|
+
| 1.0 | 500,80 | 652,191 | 594,369 | 406,369 | 348,191 |
|
|
38
|
+
|
|
39
|
+
For an arbitrary value `v` on axis `i`, take the unit offset from the row above for that axis (e.g. axis 1: offset `(152, -49)` from center) and scale by `v/S`. **Drop coords as integers — fractional pixels in SVG render fine, but integers keep the file scannable.**
|
|
40
|
+
|
|
41
|
+
### Worked example (N=5)
|
|
42
|
+
|
|
43
|
+
Series `[9, 8, 9, 9, 9]` on a 0–10 scale becomes:
|
|
44
|
+
|
|
45
|
+
```svg
|
|
46
|
+
<polygon points="500,96 622,201 585,356 415,356 363,196"
|
|
47
|
+
fill="{accent @ 0.18}" stroke="{accent}" stroke-width="1.8"/>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Each vertex: `center + (v/10) · (outer_i − center)`, rounded to the nearest pixel.
|
|
51
|
+
|
|
52
|
+
## Series palette
|
|
53
|
+
|
|
54
|
+
The skill's "1-focal" rule still holds: `accent` is reserved for the focal series, and a small editorial palette (`series-1` through `series-5`, defined in [`style-guide.md`](style-guide.md)) covers the non-focal series. Don't reach for free-form colors.
|
|
55
|
+
|
|
56
|
+
| Slot | Token | Hue |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| Focal | `accent` | the skin's accent |
|
|
59
|
+
| 1 | `series-1` | sage |
|
|
60
|
+
| 2 | `series-2` | dusty-blue |
|
|
61
|
+
| 3 | `series-3` | mustard |
|
|
62
|
+
| 4 | `series-4` | rust-brown |
|
|
63
|
+
| 5 | `series-5` | slate |
|
|
64
|
+
|
|
65
|
+
Light and dark values for every slot live in [`style-guide.md` § Series palette](style-guide.md#series-palette-multi-series-chart-types-only).
|
|
66
|
+
|
|
67
|
+
## Anti-patterns
|
|
68
|
+
|
|
69
|
+
- **More than 5 series** → mush. Split into two charts (e.g. "best by latency" + "best by ops") or switch to a comparison table.
|
|
70
|
+
- **Axes on inconsistent native scales** (one 0–100, another 0–1) without normalization. **Always normalize to 0–N first** — radar polygons compare *shapes*, not absolute values.
|
|
71
|
+
- **Zero-baseline tricks** — starting the inner ring at v=5 to amplify differences. The grid starts at 0; if differences look small, that's the truthful reading.
|
|
72
|
+
- **Dots on every series.** Only the focal carries dots. Adding them to all 4–5 series turns the chart into a bead curtain.
|
|
73
|
+
- **Radar with 2 series** — a comparison bar chart or a 2-row table is clearer.
|
|
74
|
+
- **Non-quantitative axes.** All axes must be measurable on the same normalized scale. "Speed" + "color" + "year" mixes don't belong on a radar.
|
|
75
|
+
- **Mono-font axis labels.** Names go in the `node-name` sans family (the global rule). Mono is for technical sublabels only.
|
|
76
|
+
- **Rainbow palette.** Even with the new `series-*` tokens, you don't need all 5 in one chart — use only as many as you have non-focal entities.
|
|
77
|
+
|
|
78
|
+
## Examples
|
|
79
|
+
|
|
80
|
+
- `assets/example-radar.html` — minimal light. 4 storage backends × 5 workload dimensions, MinIO focal.
|
|
81
|
+
- `assets/example-radar-dark.html` — minimal dark, same data.
|
|
82
|
+
- `assets/example-radar-full.html` — full editorial: container framing + 4 cards (one per backend) with varied widths + footer.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Sankey / Flow-Quantity
|
|
2
|
+
|
|
3
|
+
**Best for:** showing where a *quantity* goes as it splits and merges across a small number of stages — CI compute budgets, funnel-adjacent volume flows, cost or headcount allocation. This is the one type where band **thickness carries data**; if the reader doesn't need to compare magnitudes, use process or pyramid instead.
|
|
4
|
+
|
|
5
|
+
Printed node quantities preserve their complete numeric token, including finite decimal or scientific notation before the unit suffix. A quantity must be positive for a visible node bar; signs and exponents must not be discarded while checking its scale.
|
|
6
|
+
The resulting pixels-per-unit scale and relative comparisons must also be finite; a quantity outside that arithmetic range is rejected rather than certified.
|
|
7
|
+
|
|
8
|
+
## Layout conventions
|
|
9
|
+
|
|
10
|
+
- **Exactly 3 stage columns**, left → right. No more, no less — above 3 stages, split into two linked diagrams.
|
|
11
|
+
- **Nodes are vertical bars**, `width=12`, solid `ink` fill, no stroke. Height is proportional to the quantity passing through, rounded to the nearest 4px so it stays on-grid — the rounding is a rendering step, not a data change; the true value still prints in the quantity sublabel.
|
|
12
|
+
- **Flows are filled ribbons**, not stroked lines: a single closed `<path>` per flow. Top edge is a cubic Bézier from the source node's top-offset to the target node's top-offset; bottom edge is the same curve run in reverse from target-bottom back to source-bottom. **Both** control points sit at the horizontal midpoint between the two columns, each at the y of the end it belongs to: `C midX,y0 midX,y1 targetX,y1`. That is what makes the band leave and arrive **horizontally**, so it plugs into each bar square-on. Putting the second control on the target's x instead (`C midX,y0 targetX,y1 targetX,y1`) collapses it onto the endpoint, the arrival tangent degenerates, and every ribbon meets its bar at a visible slant — it looks like the band is not attached.
|
|
13
|
+
- **No arrowheads, ever.** Direction is implied by the left-to-right column order. This is a deliberate, explicit exemption from SKILL.md §6's orthogonal-elbow-and-arrowhead rule: ribbons are area encodings, not connectors, and a marker on a filled band reads as clutter, not information.
|
|
14
|
+
- **Ribbon fill:** `muted` at `0.18` opacity for ordinary flows. The one editorial focal path (see below) uses `accent` at `0.28` opacity. A focal path may span more than one ribbon segment (e.g. two flows that both feed the same downstream node as halves of one story) — accenting every ribbon that belongs to that single path still counts as **one** focal element, not one per ribbon. Never per-flow rainbow coloring — color is reserved for the one path that deserves attention.
|
|
15
|
+
- **Node ordering minimizes crossings.** Order nodes within each column so that flows converging on (or diverging from) the same node stay in a consistent top-to-bottom sequence across columns. If two ribbons still cross more than once, reorder the nodes — don't let them tangle.
|
|
16
|
+
- **Labels:** node name in the `node-name` role 12px 600, quantity directly under it in the `sublabel` role 9px `muted`. Placement is column-specific: column 1 sits outside the bar, `text-anchor="end"`; column 2 sits in the gutter above its bar, `text-anchor="middle"`, **vertically centred in that gutter** rather than flush to the top, or the cap line grazes the bar above; column 3 sits outside the bar, `text-anchor="start"`.
|
|
17
|
+
|
|
18
|
+
Centring the column-2 label in its gutter is what makes it legible: the gutter is the gap the bands themselves leave between two nodes, so a centred label sits on clean paper and needs no backing at all. Don't reach for a mask rect here — over a filled band it reads as a hole punched in the diagram, and if the label already sits on paper it is solving nothing. If a label genuinely has nowhere clear to sit, the column is over budget: drop a node or split the diagram.
|
|
19
|
+
- **Column headers:** `eyebrow` role 8px, uppercase, tracked, centered above each column — "CI COMPUTE BUDGET / TEST STAGE / OUTCOME" style eyebrows, not full sentences.
|
|
20
|
+
- **Drawing order:** background → column headers → ordinary ribbons → the accent path's ribbon(s) (painted last among ribbons so they read on top) → node bars → node labels → legend.
|
|
21
|
+
- **Legend:** horizontal strip at the bottom per the global rule, viewBox extended ~60–100px to fit it (a Sankey's ribbons need more bottom clearance than a typical diagram because node stacks run tall). Swatches are 16×8 rectangles matching each ribbon treatment, plus an optional italic aside stating the editorial read.
|
|
22
|
+
|
|
23
|
+
## Scale rule
|
|
24
|
+
|
|
25
|
+
Pick **one px-per-unit constant `k`** for the whole diagram and apply it to every node and ribbon thickness — never a different scale per column. Round each resulting thickness to the nearest 4px (grid compliance) and reconcile the rounding at the node level, not the flow level, so a node's outgoing ribbons still sum exactly to its (rounded) height.
|
|
26
|
+
|
|
27
|
+
**When you control the numbers, choose ones that land on the grid.** At `k`, a 4px step is `4/k` units; pick quantities that are whole multiples of it and the drawn area is then exactly the printed number. Rounding is the fallback for real data you cannot choose, and it is a real cost: at `k = 0.02` a 4px step is 200 minutes, so a rounded bar can misstate its own label by up to 100. In a chart whose entire premise is that area encodes quantity, a bar that disagrees with the number printed beside it spends credibility to buy grid compliance. Never let the rounding exceed one 4px step, and never round a flow so far that a node's ribbons no longer sum to its bar.
|
|
28
|
+
|
|
29
|
+
**Minimum rendered ribbon thickness: 4px.** Anything that would round to less gets folded into an "other" band rather than drawn as an invisible hairline — a Sankey ribbon the reader can't see isn't communicating anything.
|
|
30
|
+
|
|
31
|
+
### Worked reference (k = 0.02 px/unit, budget = 12,000 CI minutes)
|
|
32
|
+
|
|
33
|
+
| Node | Quantity | Height (px) |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| CI minutes (col 1) | 12,000 | 240 |
|
|
36
|
+
| Unit tests | 5,200 | 104 |
|
|
37
|
+
| E2E | 4,000 | 80 |
|
|
38
|
+
| Build | 2,000 | 40 |
|
|
39
|
+
| Lint | 800 | 16 |
|
|
40
|
+
| Passed | 9,400 | 188 |
|
|
41
|
+
| Failed | 1,600 | 32 |
|
|
42
|
+
| Flaked | 1,000 | 20 |
|
|
43
|
+
|
|
44
|
+
Each column's node heights sum to the same 240px total — that invariant (total-in equals total-out) is what makes the diagram trustworthy at a glance. If your columns don't sum to the same total, the data has a leak or the layout has a bug.
|
|
45
|
+
|
|
46
|
+
## Complexity budget
|
|
47
|
+
|
|
48
|
+
| Limit | Rule |
|
|
49
|
+
|---|---|
|
|
50
|
+
| Max stage columns | 3 |
|
|
51
|
+
| Max nodes | 8 |
|
|
52
|
+
| Max flows (ribbons) | 12 |
|
|
53
|
+
| Max accent elements | 2 (a focal path's ribbons count as one, regardless of how many segments it spans) |
|
|
54
|
+
|
|
55
|
+
Over budget → split into two linked Sankeys (e.g. an overview stage-1→stage-2 diagram plus a detail stage-2→stage-3 diagram) rather than cramming a fourth column or a ninth node into one canvas.
|
|
56
|
+
|
|
57
|
+
## Anti-patterns
|
|
58
|
+
|
|
59
|
+
- **Ribbons that meet the bar at a slant.** The single most common way to get this type wrong, and it is a control-point mistake, not a layout one: both controls belong on the midpoint x. A band arriving at an angle reads as detached from its node, which undermines the one thing the diagram is asserting — that this quantity flows into that node.
|
|
60
|
+
- **An opaque mask rect behind a column-2 label.** Over a filled band it reads as a hole punched in the diagram, and it erases any bar it overlaps. Centre the label in the gutter instead, where it sits on clean paper and needs nothing behind it.
|
|
61
|
+
- **Column-2 labels flush to the top of their gutter.** Centre them, or the cap line touches the bar above and the label looks like it belongs to the wrong node.
|
|
62
|
+
- **A bar whose height disagrees with the number printed beside it.** See the scale rule: choose grid-friendly quantities when the data is yours to choose.
|
|
63
|
+
- **Ribbons crossing more than once.** Reorder the nodes in the offending column; a Sankey with a tangle in the middle is unreadable.
|
|
64
|
+
- **Ribbons thinner than 4px.** Merge them into an "other" band instead of drawing a sliver no one can see.
|
|
65
|
+
- **Per-flow rainbow coloring.** One `muted` treatment for ordinary flows, one `accent` treatment for the flow that carries the editorial point — never a distinct hue per ribbon.
|
|
66
|
+
- **Using Sankey for a simple funnel.** A single narrowing quantity with no splits or merges is a pyramid/funnel, not a Sankey.
|
|
67
|
+
- **Using Sankey for a plain step sequence.** If nothing splits, merges, or varies in thickness, it's a process diagram — the whole point of Sankey is quantity that branches.
|
|
68
|
+
- **Stacking two flows at the same node-edge offset.** Every flow gets its own offset range within the node's height, in a consistent top-to-bottom order — never two ribbons overlapping at the same attach point.
|
|
69
|
+
- **Percentages or quantities that don't sum to the source total.** Every node's outgoing (or incoming) flows must sum to that node's own height. A Sankey that doesn't balance reads as an error, not a design choice.
|
|
70
|
+
|
|
71
|
+
## Examples
|
|
72
|
+
|
|
73
|
+
- `assets/example-sankey.html` — minimal light. A month of CI compute (12,000 minutes) splitting into test/build/lint stages, merging into passed/failed/flaked outcomes; the flaky-rerun path is the one accent flow.
|
|
74
|
+
- `assets/example-sankey-dark.html` — minimal dark, same data.
|
|
75
|
+
- `assets/example-sankey-full.html` — full editorial: container framing + 3 summary cards of varied widths + footer.
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# Scatter Plot
|
|
2
|
+
|
|
3
|
+
**Best for:** correlation and distribution — two continuous variables plotted against each other. Use when the relationship (or lack of one) between variables is the message, or when you need to identify clusters, outliers, and high/low performers.
|
|
4
|
+
|
|
5
|
+
## Layout conventions
|
|
6
|
+
|
|
7
|
+
- **Plot area margins:** left 80px, bottom 60px, top 40px, right 40px — inside `0 0 1000 500` viewBox.
|
|
8
|
+
- **Point count:** 5–30 points. Fewer → just describe the relationship in prose; more → bin into a density contour.
|
|
9
|
+
- **Axes:** X at y=420 (baseline), Y at x=80. Both use `arrow-label` role 8px gridline labels. Gridlines 4–6 per axis at equal intervals.
|
|
10
|
+
- **Point shape:** `<circle>` r=5 for standard points, r=6 for focal. Focal point in `accent` fill. Others in `muted @ 0.20` fill + `muted` stroke.
|
|
11
|
+
- **Labels on points (optional):** `arrow-label` role 8px next to a point. Use a paper-fill rect mask behind the label. Label at most 2–3 points; not all.
|
|
12
|
+
- **Trend line (optional):** `<line>` from lower-left to upper-right, stroke `ink @ 0.25` dashed 4,3. Never force a perfect fit — only add if the trend is visually obvious.
|
|
13
|
+
- **Quadrant dividers (optional):** light dashed lines at the median x and y to split into quadrants. Label each quadrant in the `arrow-label` role at 8px, `muted`.
|
|
14
|
+
|
|
15
|
+
### Point pattern
|
|
16
|
+
|
|
17
|
+
```svg
|
|
18
|
+
<!-- Non-focal point — paper mask + circle -->
|
|
19
|
+
<circle cx="X" cy="Y" r="5" fill="{paper}"/>
|
|
20
|
+
<circle cx="X" cy="Y" r="5" fill="{muted @ 0.20}" stroke="{muted}" stroke-width="1"/>
|
|
21
|
+
|
|
22
|
+
<!-- Focal point -->
|
|
23
|
+
<circle cx="X" cy="Y" r="6" fill="{paper}"/>
|
|
24
|
+
<circle cx="X" cy="Y" r="6" fill="{accent @ 0.15}" stroke="{accent}" stroke-width="1.2"/>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Anti-patterns
|
|
28
|
+
|
|
29
|
+
- More than 30 points without clustering (jitter/mush) — when the crowd itself is the story, hand it to the **beeswarm variant** below, which packs instead of jittering.
|
|
30
|
+
- Forced trend line when the data is genuinely scattered — dishonest.
|
|
31
|
+
- Point labels on every point (label the focal and 1–2 notable outliers only).
|
|
32
|
+
- Ad-hoc bubble size encoding on a plain scatter. Size perception is unreliable enough that it earns its own contract: when the third value genuinely matters, use the **bubble variant** below, which pins area to the value and gates it with `scripts/verify-bubble.py`; when it doesn't, a third axis label or a focal choice says it cheaper.
|
|
33
|
+
- Axes that don't include zero when the absolute position matters; axes that do include zero when the range is tiny and far from zero.
|
|
34
|
+
|
|
35
|
+
### Bubble
|
|
36
|
+
|
|
37
|
+
**Best for:** three quantities per item — x, y, and a magnitude — where the *joint* reading is the story: which items combine a bad position with a big footprint. The shipped example plots services by p95 latency and error rate with area as request volume, and the point of the figure is exactly the multiplication a two-variable scatter cannot do — the slowest service barely matters and the risky one is not the slowest.
|
|
38
|
+
|
|
39
|
+
Not for: a third value that is really a category (use the focal accent or facet it); two variables (that is the parent scatter — size on everything is decoration); or magnitudes spanning several orders (the small bubbles vanish; bin, or plot the magnitude on an axis of its own).
|
|
40
|
+
|
|
41
|
+
#### Layout conventions
|
|
42
|
+
|
|
43
|
+
- **Same plot frame as the parent:** margins left 80, bottom 60, top 40, right 40 inside `0 0 1000 500`; X rule at `y=420`, Y rule at `x=80`; gridlines at the parent's positions; legend on the house rhythm (rule `y=462`, `LEGEND` at `478`, keys at `490`).
|
|
44
|
+
- **Item count:** 5–15. Below 5 the leave-one-out scale check has nothing to hold onto and a table says it better; above 15 the areas start stacking and the reading degrades to a density cloud — which is the parent's contour territory, not this.
|
|
45
|
+
- **Radius from area:** `r = K·√value` for one constant K across the figure, sized so the largest bubble stays inside the plot (the shipped example uses `K = 1.4` on requests-per-second, giving 10.8–42px). State the area scale in the source line.
|
|
46
|
+
- **Bound axis ticks:** every tick carries `data-tick` (axis) and `data-value` (the number it prints). 4–6 per axis at equal intervals, `arrow-label` role 8px, same placement as the parent.
|
|
47
|
+
- **Paper underlay per bubble**, same radius, painted immediately beneath — the translucent fill must not show gridlines through itself, because the fill's job is to read as one solid area.
|
|
48
|
+
- **Draw order: largest first.** A small bubble painted early is buried under a later giant and its area is unreadable. `verify-bubble.py` checks paint order on every overlapping pair.
|
|
49
|
+
- **Labels:** the focal bubble plus at most 2–3 outliers a reader will look for, `arrow-label` role 8px small-caps on a paper mask, each bound to its bubble with `data-name`. Never all of them.
|
|
50
|
+
- **4px grid** applies to the designed constants — axis rules, gridlines, tick baselines, legend rows. Bubble centres and radii are data-scaled and exempt; snapping them would move the data.
|
|
51
|
+
|
|
52
|
+
#### Colour
|
|
53
|
+
|
|
54
|
+
- **One accent bubble, and an `ink` opacity ramp for everything else** — never a hue per item. Every labelled bubble is named where it sits, so hue would re-encode what the labels already carry.
|
|
55
|
+
- **The accent marks the editorially focal item, not the biggest or the worst single number.** In the shipped example it marks the service whose *combination* is the risk: near-peak volume on the worst error rate.
|
|
56
|
+
- **The ramp runs faintest-on-largest** (0.14 on the largest fill up to 0.35 on the smallest in the shipped example). This is ink-mass compensation, not an encoding: a giant bubble at the same opacity as a small one dominates the page by area alone, so opacity scales down as area scales up and every bubble ends up with comparable visual weight. Tone is **not** a fourth variable — the legend must say which end of the ramp is which, in skin-neutral terms ("faintest fill is the largest bubble" survives both skins; "darkest" ships false on one of them).
|
|
57
|
+
- **Every bubble keeps a `muted` stroke** (6.11:1 on light paper, 7.07:1 on dark) — the fills sit at opacities well under 3:1, so the stroke is what carries WCAG 1.4.11 for the mark's edge. The focal bubble's accent stroke measures 4.71:1 on light paper with the default skin and may measure less on an onboarded one; as with the focal bar, line and slopegraph, its data is carried redundantly — position, label, and the legend naming it in words — and the accent adds only *which bubble is focal*.
|
|
58
|
+
- **Labels stay `ink` or `muted`**, including the focal one. Accent text at 8px misses AA on light paper.
|
|
59
|
+
|
|
60
|
+
#### Honest-data rule
|
|
61
|
+
|
|
62
|
+
**Area encodes the third value — never radius.** Radius-proportional sizing squares the claim: a 6× value reads as 36× the ink. `scripts/verify-bubble.py` gates it, along with the two axis scales.
|
|
63
|
+
|
|
64
|
+
- **One finite, nonzero linear scale per axis, every bubble on it.** A bubble nudged aside because two crowd each other reads as a different number; crowded bubbles are data, and the honest fixes are a hairline of separation (which the largest-first rule provides) or fewer items — never a moved centre.
|
|
65
|
+
- **Axes include zero, or the source line states the bounds.** A bubble's position is read against the origin in a way a slopegraph's is not. No log scale without saying so — and area next to a log axis is a reading most audiences get wrong, so prefer not at all.
|
|
66
|
+
- **Omitted items are counted in the footnote.** A bubble chart with the inconvenient giant quietly missing is the same lie as a truncated axis.
|
|
67
|
+
- **A non-positive magnitude cannot be a bubble.** Area has no sign; omit the item and say so.
|
|
68
|
+
- **Round once, then draw from the rounded number**, so the declared value and the drawn geometry are two statements of one number rather than two chances to disagree.
|
|
69
|
+
|
|
70
|
+
#### Declaring the values
|
|
71
|
+
|
|
72
|
+
**Every drawn quantity is bound to an attribute stating the value it encodes.** A zero-slope value axis collapses distinct values to one position and is rejected, including in the beeswarm variant. The data circle carries all three values; the paper underlay is scenery and carries nothing.
|
|
73
|
+
|
|
74
|
+
```svg
|
|
75
|
+
<!-- A bubble: position from two shared linear scales, area from the size.
|
|
76
|
+
x = 80 + 1.76·ms, y = 420 - 95·pct, r = 1.4·√(req/s) -->
|
|
77
|
+
<circle cx="537.6" cy="154" r="38.6" fill="{paper}"/>
|
|
78
|
+
<circle data-name="Payments" data-x="260" data-y="2.8" data-size="760"
|
|
79
|
+
cx="537.6" cy="154" r="38.6"
|
|
80
|
+
fill="{accent @ 0.15}" stroke="{accent}" stroke-width="1.2"/>
|
|
81
|
+
|
|
82
|
+
<!-- Its label, bound to the bubble it names -->
|
|
83
|
+
<text data-name="Payments" data-role="label" x="538" y="108" fill="{ink}" font-size="8" font-family="{arrow-label}" text-anchor="middle" letter-spacing="0.06em">PAYMENTS</text>
|
|
84
|
+
|
|
85
|
+
<!-- An axis tick, bound to the number it prints -->
|
|
86
|
+
<text data-tick="x" data-value="300" x="608" y="440" fill="{muted}" font-size="8" font-family="{arrow-label}" text-anchor="middle">300</text>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
What each binding buys, and what it costs to omit:
|
|
90
|
+
|
|
91
|
+
| Binding | Without it |
|
|
92
|
+
|---|---|
|
|
93
|
+
| `data-x` / `data-y` on the circle | A nudged centre could not be caught — the drawn position would be the only statement of the value. |
|
|
94
|
+
| `data-size` on the circle | Nothing pins area to the value, and radius-proportional sizing renders identically plausible. |
|
|
95
|
+
| `data-name` on the circle | An unnamed bubble cannot be labelled or cross-checked, and drops out of every comparison silently. |
|
|
96
|
+
| `data-name` on a label | Two labels could be exchanged between bubbles, renaming both, with every number still correct in isolation. |
|
|
97
|
+
| `data-tick` / `data-value` on a tick | The printed axis could be relabelled wholesale — every bubble honestly placed on a scale the axis lies about. |
|
|
98
|
+
|
|
99
|
+
`scripts/verify-bubble.py` derives both axis scales and the area constant from the set itself (Theil–Sen, leave-one-out, so one dishonest bubble cannot drag the line it is measured against), requires at most one accent bubble, checks paint order on overlaps, and holds every bound label and tick against the mark it describes. Deliberately **not** `data-series`: that attribute is the slopegraph contract, and using it here would put every bubble file inside `verify-slopegraph.py`'s scope.
|
|
100
|
+
|
|
101
|
+
**No `transform` on any of it.** The checker reads raw `cx`/`cy`/`r` and `x`/`y` attributes, so a transform on a bubble, a bound label, an ancestor `<g>`, or in a CSS rule moves the rendered mark away from the number that was verified. Bake the offset into the coordinates. The rotated value-axis caption is fine — it is neither verified geometry nor a bound label.
|
|
102
|
+
|
|
103
|
+
#### Anti-patterns
|
|
104
|
+
|
|
105
|
+
- Radius proportional to the value — the type's one unforgivable error.
|
|
106
|
+
- Moving a bubble to stop it overlapping a neighbour, or drawing a small bubble under a large one.
|
|
107
|
+
- A hue per item instead of the ink ramp plus a single accent, or a legend that reads the ramp as data.
|
|
108
|
+
- A log axis the source line never states, or a truncated axis with no stated bounds.
|
|
109
|
+
- Labelling every bubble (the focal plus 2–3 outliers, no more).
|
|
110
|
+
- A `transform` on a bubble, a bound label, an ancestor group, or in CSS.
|
|
111
|
+
- An unbound visible string: a label or an axis tick with no attribute stating the same thing.
|
|
112
|
+
- Size on a value nobody will read — if the areas do not change the story, this is the parent scatter wearing a costume.
|
|
113
|
+
|
|
114
|
+
### Beeswarm
|
|
115
|
+
|
|
116
|
+
**Best for:** a full distribution where every unit deserves its own mark — one dot per item on a single shared value axis, dodged perpendicular so nothing overlaps. Use when the *shape* of the crowd and its outliers are the story a summary number would hide: the shipped example plots 138 per-request latency samples for one endpoint, and the point of the figure is that a healthy median and an ugly p99 are the same dataset.
|
|
117
|
+
|
|
118
|
+
Not for: two variables (that is the parent scatter); comparing distributions across many groups (facet, a ridgeline, or the **box plot variant** below); more than ~300 items (the packing outgrows the band — bin into a histogram); or a handful of values (below 20 there is no distribution to swarm, and a table says it shorter).
|
|
119
|
+
|
|
120
|
+
#### Layout conventions
|
|
121
|
+
|
|
122
|
+
- **One value axis, horizontal, at the parent's baseline** (`y=420` inside `0 0 1000 500`, plot margins left 80, right 40), with 4–6 bound ticks at equal intervals in the `arrow-label` role at 8px and **vertical gridlines only** — the swarm axis has no scale to grid, and a horizontal rule through the band would invite reading the packing offsets as values.
|
|
123
|
+
- **Dot count: 20–300**, one dot per item, all at one radius (the shipped example uses `r=4`). Both ends of the budget are enforced by `scripts/verify-beeswarm.py`.
|
|
124
|
+
- **Greedy dodge around a midline** (`y=230` in the shipped example): each dot takes the first free slot alternating above/below at a fixed pitch of `2r+2`. The dodge is packing, not data — any collision-free arrangement is legitimate, and the algorithm is not part of the contract.
|
|
125
|
+
- **Labels: the focal dot plus the outliers a reader will look for, at most 6.** `arrow-label` role 8px small-caps on a paper mask, one tier per label, alternating sides of the band, each tied to its dot by an unbound hairline leader and bound to it with `data-name`.
|
|
126
|
+
- **4px grid** applies to the designed constants — the axis rule, gridlines, tick baselines, legend rows. Dot positions are data-scaled on the value axis and packing-scaled on the swarm axis, and both are exempt; snapping them would move the data.
|
|
127
|
+
|
|
128
|
+
#### Colour
|
|
129
|
+
|
|
130
|
+
- **One ink fill for every non-focal dot** (`ink` at 0.55 in the shipped example) plus a `muted` stroke for the mark's edge, and **at most one accent dot**. Density must read as swarm *thickness*, never as tone: opacity as a value encoding while also dodging says one thing twice in two different lies, so `verify-beeswarm.py` requires the non-focal fill to be literally identical across the swarm.
|
|
131
|
+
- **The accent marks the editorially focal item** — in the shipped example the single slowest request, the one the title is about — never a hue per group. Groups are a facet decision, not a palette decision.
|
|
132
|
+
- **The focal dot keeps the shared radius.** Its cues are the accent fill and stroke, its label, and the legend naming it in words; a bigger focal dot is a second encoding and breaks the packing.
|
|
133
|
+
|
|
134
|
+
#### Honest-data rule
|
|
135
|
+
|
|
136
|
+
**The value axis is exact and shared; the swarm axis carries no meaning — and says so.** The legend or source line states that the vertical spread is packing, states the axis bounds, and counts anything omitted. `scripts/verify-beeswarm.py` gates the geometry half.
|
|
137
|
+
|
|
138
|
+
- **No dot is dropped, binned, or jittered off its true value.** One dot = one item at exactly its value on one linear scale derived from the set itself (Theil-Sen, leave-one-out, so one dishonest dot cannot drag the line it is measured against).
|
|
139
|
+
- **Overlap is resolved by the perpendicular dodge, never by moving a dot along the value axis.** Two items with one value share a position and dodge apart; crowding is data, and the honest rendering of a crowd is thickness.
|
|
140
|
+
- **No two dots overprint.** A dot painted over another turns density into darkness, which the eye reads as a value nobody declared.
|
|
141
|
+
- **Linear scale only in the shipped grammar.** A log axis needs its own declaration before the checker could hold it to anything, so it is refused rather than half-trusted; if the spread demands log, say so in the source line and expect the gate to disagree until the grammar grows the declaration.
|
|
142
|
+
|
|
143
|
+
#### Declaring the values
|
|
144
|
+
|
|
145
|
+
**Every dot is bound to the value it encodes.** `data-value` on a `<circle>` is the beeswarm contract — deliberately not `data-series` (slopegraph), `data-ranks` (bump) or `data-size` (bubble), so no sibling checker claims a beeswarm file and this one claims none of theirs. Detection is element-scoped: `data-value` also appears on `<text>` axis ticks in the bubble contract, so only a `<circle>` binding it puts a file inside this gate. The paper underlay beneath each dot is scenery and carries nothing.
|
|
146
|
+
|
|
147
|
+
```svg
|
|
148
|
+
<!-- A dot: position on the shared value scale, x = 80 + 2·ms. The cy is
|
|
149
|
+
packing only. -->
|
|
150
|
+
<circle data-value="90" cx="260" cy="250" r="4" fill="{ink @ 0.55}" stroke="{muted}" stroke-width="0.75"/>
|
|
151
|
+
|
|
152
|
+
<!-- The focal dot — named, accented, same radius as everyone -->
|
|
153
|
+
<circle data-value="431" data-name="req-4c1f" cx="942" cy="230" r="4" fill="{accent @ 0.55}" stroke="{accent}" stroke-width="1.2"/>
|
|
154
|
+
|
|
155
|
+
<!-- Its label, bound to the dot it names -->
|
|
156
|
+
<text data-name="req-4c1f" data-role="label" x="942" y="120" fill="{ink}" font-size="8" font-family="{arrow-label}" text-anchor="middle" letter-spacing="0.06em">REQ-4C1F</text>
|
|
157
|
+
|
|
158
|
+
<!-- An axis tick, bound to the number it prints -->
|
|
159
|
+
<text data-tick="x" data-value="200" x="480" y="440" fill="{muted}" font-size="8" font-family="{arrow-label}" text-anchor="middle">200</text>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
What each binding buys, and what it costs to omit:
|
|
163
|
+
|
|
164
|
+
| Binding | Without it |
|
|
165
|
+
|---|---|
|
|
166
|
+
| `data-value` on the circle | A dot nudged along the value axis could not be caught — the drawn position would be the only statement of the value. |
|
|
167
|
+
| `data-name` on the focal/outlier circles | An unnamed outlier cannot be labelled or cross-checked, and two swapped labels rename both dots silently. The name must be non-empty and unique to one dot: two dots sharing a name collapse into one entry, so a single label satisfies both. |
|
|
168
|
+
| `data-name` on a label | The label floats free — it could name a dot that is not there, or drift to a neighbour. |
|
|
169
|
+
| `data-tick` / `data-value` on a tick | The printed axis could be relabelled wholesale — every dot honestly placed on a scale the axis lies about. |
|
|
170
|
+
|
|
171
|
+
`scripts/verify-beeswarm.py` derives the value scale from the dots themselves, requires dots sharing a value to share a position, refuses any overprinted pair, holds every dot to one radius and every non-focal dot to one fill, caps the accent at one dot and the labels at six, requires every `data-name` to be non-empty and unique, and checks every bound label and tick against the mark it describes; `scripts/test-verify-beeswarm.py` proves each check in both polarities and pins the sibling scope treaty in both directions.
|
|
172
|
+
|
|
173
|
+
**Nothing may position a mark except its own attributes.** The checker reads raw `cx`/`cy`/`r` and `x`/`y`, so anything applied afterwards invalidates the check that passed. Every carrier is refused — the `transform` attribute, an inline `style="…"`, and a rule in a `<style>` block — on a dot, on a bound label or tick, or on an ancestor `<g>`/`<svg>`. So is every positioning property, not just `transform`: the Level 2 individual properties `translate`/`rotate`/`scale`, the SVG geometry properties `cx`/`cy`/`r`/`x`/`y` (CSS beats the presentation attribute), and CSS motion path. Bake the offset into the coordinates. `line-height`, `color` and font declarations are untouched — they do not move anything.
|
|
174
|
+
|
|
175
|
+
#### Anti-patterns
|
|
176
|
+
|
|
177
|
+
- Moving a dot along the value axis to open up space — the type's one unforgivable error.
|
|
178
|
+
- Binning or averaging before plotting (that is a histogram wearing a costume), or quietly dropping the inconvenient outlier.
|
|
179
|
+
- Dot size as a second encoding (that is the **bubble variant**), or opacity as a value encoding while also dodging.
|
|
180
|
+
- Meaning smuggled into the swarm axis: sorting the dodge by a second variable, or a midline drawn as if it were a scale.
|
|
181
|
+
- A hue per group instead of one ink plus a single accent.
|
|
182
|
+
- Labelling more than the focal dot and a handful of outliers.
|
|
183
|
+
- Positioning a dot, a bound label, a tick or an ancestor group from CSS — `transform`, `translate`, `rotate`, `scale`, a geometry property (`cx`/`cy`/`r`/`x`/`y`) or a motion path — whether as an attribute, an inline `style`, or a rule in a `<style>` block.
|
|
184
|
+
- Two dots wearing the same `data-name`, or an empty one: a name that identifies two marks identifies neither.
|
|
185
|
+
- An unbound visible string: a label or an axis tick with no attribute stating the same thing.
|
|
186
|
+
|
|
187
|
+
### Box plot
|
|
188
|
+
|
|
189
|
+
**Best for:** comparing the *shape* of a few distributions when the five-number summary is the story — one box per group on a single shared value axis. Use it when a median would hide the spread, and when drawing every unit (the beeswarm) would hide the comparison: the shipped example plots per-request latency for five services, and the point of the figure is that Checkout's median still looks survivable while its box already runs 80–200 ms.
|
|
190
|
+
|
|
191
|
+
Not for: two continuous variables (that is the parent scatter); one distribution of individuals (that is the **beeswarm variant**); the silhouette of many distributions (that is the **ridgeline** on Line); a single number per category (that is a bar).
|
|
192
|
+
|
|
193
|
+
#### Layout conventions
|
|
194
|
+
|
|
195
|
+
- **Orientation:** horizontal only. Rows stack downward and the value axis runs left → right; a vertical box plot forces rotated category labels, which this variant does not.
|
|
196
|
+
- **Same plot frame as the parent:** value axis at `y=420` inside `0 0 1000 500`. Category labels take a 160px left margin (right-aligned at `x=148`, `node-name` role 11px 600), so the scale starts at `x=160`. The shipped example uses `x = 160 + 2·ms` on a 0–400 domain.
|
|
197
|
+
- **Series count: 3–8.** Below 3 a table says it shorter; above 8 split an overview from per-series detail.
|
|
198
|
+
- **One box per series**, height 32px, centred on the row. Paper underlay immediately beneath so gridlines do not show through the translucent fill.
|
|
199
|
+
- **Tukey fences, not the theoretical fence:** whiskers run to the last inlier (`data-wlo` / `data-whi`) inside `q1 - 1.5·IQR` and `q3 + 1.5·IQR`, with 16px caps. Outliers are `r=4` dots strictly outside those whiskers and fences, bound with `data-x` on the circle — never `data-value`, which is the beeswarm contract.
|
|
200
|
+
- **Median** is a vertical `ink` line through the box at `data-q2`, 1.5px. It is the only mark that may sit on the `ink` token at full strength; the box fill stays a tint.
|
|
201
|
+
- **Labels:** the category name at the left of its row, bound with `data-name`. Value-axis ticks are bound with `data-tick="x"` and `data-value`. The category axis has no scale to tick.
|
|
202
|
+
- **4px grid** applies to the designed constants — axis rule, gridlines, tick baselines, legend rows, box height, row centres. Positions on the value axis are data-scaled and exempt.
|
|
203
|
+
|
|
204
|
+
#### Colour
|
|
205
|
+
|
|
206
|
+
- **One accent series, and one muted treatment for everyone else.** The shipped example accents Checkout — the IQR that will page — not the series with the most outliers.
|
|
207
|
+
- **Fill weight, not hue, carries the pairing.** Non-focal boxes use `muted @ 0.15` fill + `muted` stroke; the focal box uses `accent @ 0.12` fill + `accent` stroke. Outliers inherit their series' stroke. The median stays `ink` on every row so it does not compete with the focal fill.
|
|
208
|
+
- **The value axis is exact and shared.** Crowded boxes mean the summaries really are close; never nudge a box along the value axis to open up space.
|
|
209
|
+
|
|
210
|
+
#### Honest-data rule
|
|
211
|
+
|
|
212
|
+
**The five-number summary is ordered, and every mark sits on one linear scale.** `scripts/verify-box-plot.py` gates it.
|
|
213
|
+
|
|
214
|
+
- **`wlo ≤ q1 ≤ q2 ≤ q3 ≤ whi`.** A median outside its box, or a whisker that grows inward, is not a five-number summary.
|
|
215
|
+
- **Box left is q1, box right is q3, median is q2, whiskers are wlo/whi.** Width encodes the IQR — never pad a box to fit a label.
|
|
216
|
+
- **Outliers sit strictly outside the whiskers and the Tukey fences.** Drawing an inlier as a dot, or parking an outlier on the fence, hides the tail.
|
|
217
|
+
- **Linear scale only, from a stated floor.** The shipped example includes zero. A truncated axis inflates every box the same way a truncated bar does.
|
|
218
|
+
- **Round once, then draw from the rounded number**, so the declared value and the drawn geometry are two statements of one number.
|
|
219
|
+
|
|
220
|
+
#### Declaring the values
|
|
221
|
+
|
|
222
|
+
**Every series is bound to the five numbers it encodes.** `data-q1` on a `<g>` is the box-plot contract — deliberately not `data-value` on a `<circle>` (beeswarm), `data-size` (bubble), `data-series` (slopegraph) or `data-ranks` (bump), so no sibling checker claims a box-plot file and this one claims none of theirs.
|
|
223
|
+
|
|
224
|
+
```svg
|
|
225
|
+
<!-- One series. x = 160 + 2·ms. Category y is a label rail. -->
|
|
226
|
+
<g data-name="Checkout" data-q1="80" data-q2="120" data-q3="200"
|
|
227
|
+
data-wlo="40" data-whi="280">
|
|
228
|
+
<line data-role="whisker-lo" x1="240" y1="216" x2="320" y2="216"/>
|
|
229
|
+
<line data-role="whisker-hi" x1="560" y1="216" x2="720" y2="216"/>
|
|
230
|
+
<rect data-role="box" x="320" y="200" width="240" height="32" rx="2"
|
|
231
|
+
fill="{accent @ 0.12}" stroke="{accent}" stroke-width="1.2"/>
|
|
232
|
+
<line data-role="median" x1="400" y1="200" x2="400" y2="232"/>
|
|
233
|
+
<circle data-outlier="Checkout" data-x="388" cx="936" cy="216" r="4"/>
|
|
234
|
+
</g>
|
|
235
|
+
<text data-tick="x" data-value="200" x="560" y="440">200</text>
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
What each binding buys, and what it costs to omit:
|
|
239
|
+
|
|
240
|
+
| Binding | Without it |
|
|
241
|
+
|---|---|
|
|
242
|
+
| `data-q1` / `data-q2` / `data-q3` / `data-wlo` / `data-whi` on the group | A nudged box could not be caught — the drawn geometry would be the only statement of the summary. |
|
|
243
|
+
| `data-role="box"` / `"median"` / `"whisker-lo"` / `"whisker-hi"` | The checker could not tell a gridline from a whisker, or a paper underlay from the IQR. |
|
|
244
|
+
| `data-x` on an outlier circle | `data-value` on a circle is the beeswarm contract; using it here would put every box-plot file inside `verify-beeswarm.py`. |
|
|
245
|
+
| `data-name` on the group and its category label | Two labels exchanged between rows rename both series, with every number still correct in isolation. |
|
|
246
|
+
| `data-tick` / `data-value` on a tick | The printed axis could be relabelled wholesale — every box honestly placed on a scale the axis lies about. |
|
|
247
|
+
|
|
248
|
+
`scripts/verify-box-plot.py` derives the value scale from the summaries themselves, requires the five-number order, holds every box, median, whisker and outlier to that scale, refuses an outlier inside the whiskers, caps the accent at one series (read through `var()`, `hsl()` and class, id or attribute selectors), refuses a bound mark hidden by `display`, `visibility` or `opacity`, and checks every bound tick against the mark it describes; `scripts/test-verify-box-plot.py` proves each check in both polarities and pins the sibling scope treaty in both directions.
|
|
249
|
+
|
|
250
|
+
**Nothing may position a mark except its own attributes.** The checker reads raw `x`/`width`/`x1`/`cx` and `y`/`cy`, so anything applied afterwards invalidates the check that passed. Every carrier is refused — the `transform` attribute, an inline `style="…"`, and a rule in a `<style>` block — on a series group, on a box, on a bound label or tick, or on an ancestor `<g>`/`<svg>`. Bake the offset into the coordinates.
|
|
251
|
+
|
|
252
|
+
#### Anti-patterns
|
|
253
|
+
|
|
254
|
+
- Moving a box along the value axis to open up space — the type's one unforgivable error.
|
|
255
|
+
- A median outside its box, or whiskers that grow inward.
|
|
256
|
+
- Parking an outlier on the fence, or drawing an inlier as a dot.
|
|
257
|
+
- A hue per series instead of one muted treatment plus a single accent.
|
|
258
|
+
- `data-value` on an outlier circle (that is the beeswarm contract).
|
|
259
|
+
- Positioning a mark, a bound label, a tick or an ancestor group from CSS.
|
|
260
|
+
- Two series wearing the same `data-name`, or an empty one.
|
|
261
|
+
- An unbound visible string: a category label or an axis tick with no attribute stating the same thing.
|
|
262
|
+
|
|
263
|
+
## Examples
|
|
264
|
+
|
|
265
|
+
- `assets/example-scatter.html` — minimal light
|
|
266
|
+
- `assets/example-scatter-dark.html` — minimal dark
|
|
267
|
+
- `assets/example-scatter-full.html` — full editorial
|
|
268
|
+
- `assets/example-bubble.html` — bubble, minimal light
|
|
269
|
+
- `assets/example-bubble-dark.html` — bubble, minimal dark
|
|
270
|
+
- `assets/example-bubble-full.html` — bubble, full editorial
|
|
271
|
+
- `assets/example-beeswarm.html` — beeswarm, minimal light
|
|
272
|
+
- `assets/example-beeswarm-dark.html` — beeswarm, minimal dark
|
|
273
|
+
- `assets/example-beeswarm-full.html` — beeswarm, full editorial
|
|
274
|
+
- `assets/example-box-plot.html` — box plot, minimal light
|
|
275
|
+
- `assets/example-box-plot-dark.html` — box plot, minimal dark
|
|
276
|
+
- `assets/example-box-plot-full.html` — box plot, full editorial
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Sequence
|
|
2
|
+
|
|
3
|
+
**Best for:** request/response flows, protocol exchanges, multi-actor interactions over time, API call traces, incident reconstructions, auth/token refresh paths with branching.
|
|
4
|
+
|
|
5
|
+
## Layout conventions
|
|
6
|
+
- Actors as boxes in a horizontal row at the top.
|
|
7
|
+
- **Lifelines**: dashed vertical lines descending from each actor to the bottom.
|
|
8
|
+
- Messages: horizontal arrows between lifelines; time flows top→down.
|
|
9
|
+
- **Activation bar**: narrow rectangle (`w=8`, muted fill, 0.8 hairline stroke) on a lifeline spanning the interval that actor holds control. Stack for nested calls.
|
|
10
|
+
- Self-messages: short U-shaped loop returning to the same lifeline; label right of the loop.
|
|
11
|
+
- Return messages: **dashed** stroke + **filled** marker (never open). Prefer muted; optionally match the originating call color when pairing multi-hop stacks. Headline success may use solid coral (see Message kinds).
|
|
12
|
+
- Coral on the primary success response or headline message — one, maybe two. Actor focal strokes do not count toward the coral message budget.
|
|
13
|
+
- When the flow **branches** (valid vs invalid token, retry, optional step), draw a **combined fragment** frame — do not invent free-floating if/else arrow clusters.
|
|
14
|
+
|
|
15
|
+
## Message kinds
|
|
16
|
+
|
|
17
|
+
| Kind | Stroke | Marker | When |
|
|
18
|
+
|---|---|---|---|
|
|
19
|
+
| Call (sync) | solid muted or link-blue | filled | Request that expects a reply |
|
|
20
|
+
| Return | **dashed** muted (or match call color) | filled | Reply to a sync call — never solid |
|
|
21
|
+
| Async / fire-and-forget | dashed muted | **open** arrowhead | Beacons, events, one-way notify |
|
|
22
|
+
| Headline success | solid accent (≤1–2 messages) | accent filled | Primary happy-path response only |
|
|
23
|
+
|
|
24
|
+
### Open arrowhead (async)
|
|
25
|
+
|
|
26
|
+
Define once in `<defs>` and use for fire-and-forget only:
|
|
27
|
+
|
|
28
|
+
```svg
|
|
29
|
+
<marker id="arrow-open" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
|
30
|
+
<polyline points="0 0, 8 3, 0 6" fill="none" stroke="{muted}" stroke-width="1.2"/>
|
|
31
|
+
</marker>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Dark mode: `muted` inverts with the skin, so the stroke needs no change. Do not fill the open marker — the hollow head is the async signal. Return messages keep the **filled** marker even when dashed.
|
|
35
|
+
|
|
36
|
+
## Combined fragments (`alt` / `opt` / `loop`)
|
|
37
|
+
|
|
38
|
+
Use a rectangular **frame** that spans only the lifelines participating in the branch. Operator label uses the `eyebrow` role, uppercase, in a small tab at the top-left of the frame. Time still flows top→down inside the frame.
|
|
39
|
+
|
|
40
|
+
### Frame primitive (shared)
|
|
41
|
+
|
|
42
|
+
```svg
|
|
43
|
+
<!-- Frame: light ink wash + hairline. Label tab top-left. -->
|
|
44
|
+
<rect x="X" y="Y" width="W" height="H" rx="4"
|
|
45
|
+
fill="{ink @ 0.02}" stroke="{ink @ 0.22}" stroke-width="1"/>
|
|
46
|
+
<!-- Operator tab -->
|
|
47
|
+
<rect x="X" y="Y" width="40" height="16" rx="2"
|
|
48
|
+
fill="{paper}" stroke="{ink @ 0.22}" stroke-width="1"/>
|
|
49
|
+
<text x="X+20" y="Y+12" fill="{muted}" font-size="8"
|
|
50
|
+
font-family="{eyebrow}" text-anchor="middle"
|
|
51
|
+
letter-spacing="0.12em">ALT</text>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Dark mode: the roles invert with the skin, so only the frame fill changes, to `ink @ 0.04` from `ink @ 0.02`. Stroke stays `ink @ 0.22`, tab fill stays `paper`, tab text stays `muted`.
|
|
55
|
+
|
|
56
|
+
### Operators
|
|
57
|
+
|
|
58
|
+
| Operator | Regions | Divider | Guard label |
|
|
59
|
+
|---|---|---|---|
|
|
60
|
+
| `opt` | 1 | none | `[if condition]` under the tab (`arrow-label` role, 8px) |
|
|
61
|
+
| `alt` | **2 max** | dashed horizontal hairline across the frame | `[guard]` on region 1; `[else]` (or a second guard) on region 2 |
|
|
62
|
+
| `loop` | 1 | none | `[for each item]` or `[retry ≤ 3]` under the tab |
|
|
63
|
+
|
|
64
|
+
### Guard + divider primitives
|
|
65
|
+
|
|
66
|
+
```svg
|
|
67
|
+
<!-- Guard: left-aligned inside the frame, mono -->
|
|
68
|
+
<text x="X+12" y="GUARD_Y" fill="{muted}" font-size="8"
|
|
69
|
+
font-family="{arrow-label}" letter-spacing="0.04em">[token valid]</text>
|
|
70
|
+
|
|
71
|
+
<!-- alt region divider -->
|
|
72
|
+
<line x1="X+8" y1="DIV_Y" x2="X+W-8" y2="DIV_Y"
|
|
73
|
+
stroke="{ink @ 0.20}" stroke-width="1" stroke-dasharray="4,3"/>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Fragment layout rules
|
|
77
|
+
- Frame left/right inset ≥12px from the outermost participating lifeline centers (so activation bars stay inside the frame).
|
|
78
|
+
- ≥24px between consecutive message y-levels inside a region (4px grid).
|
|
79
|
+
- Guard sits in the first ~20px under the tab; first message in that region is ≥24px below the guard baseline.
|
|
80
|
+
- Divider y on the 4px grid; ≥16px clear of messages above and below.
|
|
81
|
+
- Nested fragments: **max 1 level**. Prefer two separate diagrams over deep nesting.
|
|
82
|
+
- Default: **one** fragment per diagram. A second only if both stay under the complexity budget.
|
|
83
|
+
- Coral stays on **one** headline success message across the whole diagram (usually the happy-path return inside the first `alt` region, or the final success outside a loop). Do not coral both `alt` branches.
|
|
84
|
+
|
|
85
|
+
### Out of scope (do not invent)
|
|
86
|
+
- `par`, `critical`, `break`, `ref`, and other UML operators — second PR if needed.
|
|
87
|
+
- Participant create/destroy, found/lost messages, duration timing bars.
|
|
88
|
+
|
|
89
|
+
## Complexity budget (sequence-specific)
|
|
90
|
+
- Max lifelines: 5 (same as the §7 budget table in [layout-budget.md](layout-budget.md#complexity-budget-per-diagram)).
|
|
91
|
+
- Max messages (arrows): 12.
|
|
92
|
+
- Max combined fragments: 1 (hard default); 2 only if each is a single-region `opt`/`loop`.
|
|
93
|
+
- Max `alt` regions: 2.
|
|
94
|
+
- Max fragment nesting depth: 1.
|
|
95
|
+
- Max coral elements: 2 (prefer 1 for fragment diagrams).
|
|
96
|
+
|
|
97
|
+
If you exceed, split: overview (happy path) + detail (failure / refresh path).
|
|
98
|
+
|
|
99
|
+
## Lifeline primitive
|
|
100
|
+
```svg
|
|
101
|
+
<line x1="CX" y1="TOP" x2="CX" y2="BOTTOM"
|
|
102
|
+
stroke="{ink @ 0.20}" stroke-width="1" stroke-dasharray="3,3"/>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Activation bar primitive
|
|
106
|
+
```svg
|
|
107
|
+
<rect x="CX-4" y="TOP" width="8" height="H"
|
|
108
|
+
fill="{ink @ 0.06}" stroke="{muted}" stroke-width="0.8"/>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Anti-patterns
|
|
112
|
+
- Message arrow pointing *upward* (reverses time — never).
|
|
113
|
+
- Activation bars that never close.
|
|
114
|
+
- Labels sitting over another lifeline — shorten or shift y into a gap.
|
|
115
|
+
- Swimlane-style lanes instead of lifelines (different grammar).
|
|
116
|
+
- Drawing `if/else` as two free-floating arrow clusters with **no** fragment frame.
|
|
117
|
+
- Nested `alt` inside `alt` (split into two diagrams).
|
|
118
|
+
- Fragment operator label in the `node-name` sans family — must be mono (`eyebrow`): `ALT` / `OPT` / `LOOP`.
|
|
119
|
+
- Coral on both `alt` branches.
|
|
120
|
+
- Frame that covers actors with no messages inside the fragment.
|
|
121
|
+
- Filled arrowhead on async fire-and-forget (use open marker).
|
|
122
|
+
- Open arrowhead on return messages (returns stay filled + dashed).
|
|
123
|
+
|
|
124
|
+
## Examples
|
|
125
|
+
- `assets/example-sequence.html` — minimal light (cold-cache happy path)
|
|
126
|
+
- `assets/example-sequence-dark.html` — minimal dark
|
|
127
|
+
- `assets/example-sequence-full.html` — full editorial
|
|
128
|
+
- `assets/example-sequence-oauth.html` — special: bearer call + `alt` refresh (light)
|
|
129
|
+
- `assets/example-sequence-oauth-dark.html` — same special, dark
|
|
130
|
+
- `assets/example-sequence-oauth-full.html` — same special, full editorial
|