diagram-design
Create branded architecture, architecture delta, IT current-state, flowchart, sequence, state machine, ER/data model, timeline, swimlane, quadrant, radar/spider, polar chart (polar/radial lollipop), loop/flywheel, nested, tree, org chart, layer stack, exploded axonometric, axonometric plan, Venn, pyramid/funnel, treemap and marimekko, heatmap, bar and dumbbell, waterfall, line (slopegraph, ridgeline, streamgraph, bump), Gantt and scatter charts (bubble, beeswarm, box plot), high-level, process,
Install
npx skills add https://github.com/cathrynlavery/diagram-design --skill diagram-designDP security matrix
Best for: documenting per-role / per-component access permissions for a data platform — a grid where each row is a platform component (Keycloak, MinIO bucket, Trino catalog, JupyterHub, NiFi, …) and each column is a role / AD group (Data Administrators, Data Engineers, Data Scientists, Data Consumers, …). Each intersection cell holds a permission value (Admin / Full / R/W / Read / SELECT / Login / No access) with a visual category that matches the permission level. One cell may be marked focal to flag a critical access rule (e.g., "Data Consumers can ONLY SELECT from the aggregated catalog — sole consumer access").
Use when stakeholders need to audit who can do what across the platform. Prefer DP integration when the question is who can talk to what (topology/protocol) rather than who can write/read what (permissions).
This type is parametric — the inputs schema in §1 drives every coordinate via the formulas in §2. The rule shape mirrors type-medallion.md / type-process.md / type-data-flow.md so the focal rule, color override, dark mode, and reproducibility checklist read identically across types.
1. Inputs — the parameter contract
Reserved field semantics:
roles[j].name— primary role label (node-namerole at 11px, white text on the ink banner)roles[j].code— secondary AD-group identifier (sublabelrole, white text at 0.85 opacity)components[i].hint— optional right-alignedsublabeltext in the label cell (e.g.,"SSO","S3 API")cells[k].level— closed vocabularyfull | rw | read | none. Drives fill/stroke/text-color per §2.4.cells[k].value— free-form display text. Domain-specific labels ("R/W","SELECT","Login") work without inventing new levels.cells[k].focal: true— exactly one cell may declare this. Overrideslevelto focal styling.cells[k].sub— optional 2nd-line text (used with focal). Renders in thesublabelrole at 8px below the primary value.cells[k].color: "#hex"— optional per-cell color override (§4).
2. Layout formulas — deterministic geometry
2.1 Background
Solid paper fill across the full viewBox. No dot pattern.
2.2 Header row (y = 72, h = 52)
Component-column header cell:
- Rect:
(comp_col_x, header_y, comp_col_w, header_h), fill white, strokeink @ 0.120.8,rx=6 - Two-line label centered at
(comp_col_x + comp_col_w/2, header_y+24)and(…, header_y+40):- Line 1:
"Component"—node-namerole at 11px, ink - Line 2:
"vs. AD group"—sublabelrole, muted
- Line 1:
Role banners (one per role):
- Rect:
(role_col_x(j), header_y, role_col_w, header_h), fillink,rx=6 - Two-line label centered:
- Line 1 at
y=92:roles[j].name—node-namerole at 11px, white - Line 2 at
y=108:roles[j].code—sublabelrole, white at opacity 0.85
- Line 1 at
2.3 Data row (y = row_y(k), h = 36)
Component label cell:
- Rect:
(comp_col_x, row_y(k), comp_col_w, row_h), fill white, strokeink @ 0.120.8,rx=4 - Name at
(comp_col_x + 12, row_y(k) + 22):node-namerole at 11px, ink, left-aligned - Hint (if present) at
(comp_col_x + comp_col_w − 12, row_y(k) + 22):sublabelrole, muted, right-aligned
Value cells (one per role × component):
- Rect:
(role_col_x(j), row_y(k), role_col_w, row_h),rx=4, strokeink @ 0.120.6 - Fill and text-color depend on
level(or focal flag) — see §2.4 - Value text centered at
(role_col_cx(j), row_y(k) + 22):node-namerole at 10px - Focal cell uses a slightly raised primary text at
y=18and a sub-line aty=30(sublabelrole at 8px)
2.4 Cell style table
The focal cell can carry a 2nd line (sub:) rendered in accent with the sublabel role at 8px and 0.85 opacity.
2.5 Legend (y_top = legend_y_top, h ≈ 30)
Hairline separator at legend_y_top. Below the separator, one row of style swatches with their labels — only the styles actually used in the diagram appear in the legend.
- "LEGEND" eyebrow at
(left_pad, legend_y_top + 20):eyebrowrole, muted, letter-spacing 0.14em - Each style: swatch rect (14×12
rx=2) followed by asublabelrole label - Item x-positions are tabulated left-to-right with ~120-px stride; legend wraps onto a second visual row only if
n_roles ≥ 6(otherwise fits in one line)
3. Cells, not connectors
A matrix diagram has no connectors — there are no arrows between cells, no flow lines. The diagram's information is entirely in the cell content + cell styling. The only "connector-like" element is the focal cell's accent border, which visually "calls out" a specific intersection.
Cells emit no edges. Don't add arrows pointing into cells or between cells — they belong in a different diagram type.
4. Color overrides
Three independent override axes — per-cell, per-component (row), per-role (column). All optional, all use the same color: "#hex" field, all draw from the same recommended palette. Mirrors §3.4 of type-high-level.md, §4 of type-process.md, §4 of type-medallion.md.
4.1 Per-cell color
Tints a specific intersection cell. Applied to:
C_light = the same hex lightened ~15% for dark-mode contrast.
4.2 Per-component color (components[i].color)
Tints the row's label cell only (left column). The data cells in that row keep their per-cell level styling — the row color flags what this component is, not what permissions live in it.
4.3 Per-role color (roles[j].color)
Tints the column banner only (top row). Cells underneath keep their level styling. Replaces the default navy banner fill with the chosen hex.
If you pick a mid-luminance hex (e.g., yellow #c9a23a), the text auto-flips to ink for contrast. Pair roles[j].text_color: "#hex" to override this auto-pick.
4.4 Rules
- Focal cell wins. A focal cell ignores
coloroverrides — accent always. - Per-cell
coloroverrideslevelstyling for that one cell. - Per-component / per-role overrides are scoped: component → row label only; role → banner only. They do not spread into the matrix body. To flag a specific intersection, use per-cell.
- Cap: keep total custom-colored entities ≤ 5 per diagram (combining cells + components + roles). Above 5, the matrix reads as colored noise — split into multiple diagrams or rethink which color carries which concern.
4.5 Recommended palette (same as the other parametric types)
#b85450rust-red — Security elevation / break-glass / SoX-flagged#5a7d9aslate-blue — Quality / monitoring / observability gate#7a8c47olive-green — Approved / governance-cleared / publication-ready#c9a23awarm yellow — Working / sandbox / data-scientist zone#8c6d3fwarm-brown — Archive / cold / DR
5. Focal rule
Exactly one focal cell per diagram (or zero). The focal cell:
- Uses focal styling (accent fill + accent stroke 1.4 + accent text bold)
- May carry a 2-line content: primary
valueaty = row_y(k) + 18,subaty = row_y(k) + 30 - Calls out the diagram's central security claim — the one access rule that distinguishes this platform's posture from a generic permissions table
If zero or >1 focal: true cells are declared, halt and ask the user.
6. Dark mode
7. Reproducibility checklist (taste gate)
Before emitting SVG, verify every item:
viewBox = "0 0 {viewBox_w} {viewBox_h}"derived via §2 (4 roles × 8 components → 920 × 520).- Header row at
y=72 h=52. Component header cell white-filled with two-lineComponent / vs. AD grouplabel. Role banners filledinkwith name + AD-group code in white. - Data rows start at
y=140, stride 40, height 36.rows_bottom = 140 + (n_components−1)·40 + 36. - Component label cell
rx=4, name left-aligned atx=24, optionalhintright-aligned atx = comp_col_x + comp_col_w − 12. - Every value cell
rx=4, strokeink @ 0.120.6, fill + text matching §2.4 for itslevel. - Exactly one focal cell (or zero). Focal cell stroke
accentwidth 1.4. Primary value aty = row_y(k) + 18;sub(if present) aty = row_y(k) + 30. - Cells omitted from
cells:render aslevel: "none"withnone_labeltext (default"No access"). - Custom-colored cells ≤ 2 (in addition to the focal cell).
- No connector elements anywhere in the SVG.
- Legend strip at
legend_y_top, one swatch perlevelactually used, hairline separator above. viewBox_hgrows withn_components;viewBox_wgrows withn_roles.
8. Anti-patterns
- More than one focal cell — focal exists to mark the critical access rule; >1 erases the signal.
- Connectors anywhere — matrix is value-driven; arrows belong in DP integration / process diagrams.
- Freeform
levelvalues — closed vocabulary isfull | rw | read | none. Usevaluefor free-form displayed text +coloroverride for arbitrary tinting. - Per-row or per-column color tints — apply
colorper cell only. Whole-row or whole-column highlighting tends to over-emphasize and collapses the matrix into a list. - Using
none_labelas a placeholder for "TBD" —nonemeans no access. If the permission is unknown, leave the cell empty in inputs but document it elsewhere; don't render an ambiguous state. - More than 6 roles — split into two matrices (e.g., human roles vs service accounts) before exceeding 6 columns.
- More than 14 components — split by domain (storage / compute / observability / governance) before exceeding 14 rows.
- Using the matrix to document how permissions are granted — that belongs in a process or sequence diagram. The matrix shows what each role can do, not the grant flow.
9. Examples
assets/example-dp-security-matrix.html— minimal light (NatStat canonical: 4 roles × 8 components, focal at Data Consumers × Trino aggregated). Gallery default.assets/example-dp-security-matrix-dark.html— same, dark skin.assets/example-dp-security-matrix-full.html— same, editorial-card frame with subtitle + summary cards.
10. Worked YAML — full inputs for example-dp-security-matrix.html
The complete inputs that map to the shipped canonical example. Every coordinate in that SVG is derivable from §2 applied to these inputs.
10.1 What this YAML proves
Run §2 with these inputs:
n_roles = 4,n_components = 8, no color overrides, one focal cell.viewBox_w = 12 + 208 + 12 + 4·148 + 3·16 + 48 = 920✓row_y(k)produces140, 180, 220, 260, 300, 340, 380, 420✓rows_bottom = 420 + 36 = 456;legend_y_top = 476;viewBox_h = 520✓role_col_x(j) = [232, 396, 560, 724]✓- Focal cell at
(row=5, col=3)→ rect(724, 340, 148, 36)with accent stroke 1.4 ✓
A fresh generation from this YAML produces a diagram visually indistinguishable from the shipped example-dp-security-matrix.html.