$npx -y skills add gnurio/tufte-vdqi-plugin --skill render-tufte-chartProduce a real, publication-ready data graphic (SVG or HTML) that obeys Tufte's principles — minimal ink, range-frame axes, direct labels, honest proportions. Use whenever someone wants to design, build, create, draw, or actually produce a chart the Tufte way, or to rebuild a cha
| 1 | # Render Tufte Chart |
| 2 | |
| 3 | This skill produces an actual chart file. It is the toolkit's output stage: the |
| 4 | router sends "design / build / produce a chart" requests here, and an |
| 5 | optimisation workflow ends here (assess to diagnose, render to rebuild). |
| 6 | |
| 7 | The old version of this skill shipped Python that called undefined functions and |
| 8 | could not run. It is replaced by a working script plus clear construction guidance. |
| 9 | |
| 10 | ## Build checklist (apply every time) |
| 11 | |
| 12 | These bake in the principles from `references/tufte-principles.md` so the chart |
| 13 | never needs cleanup afterwards. Read that file for the full reasoning. |
| 14 | |
| 15 | - **Honest proportions (B1).** Bars/columns start at a zero baseline. Encode a |
| 16 | 1-D quantity with length or position, never with 2-D area or 3-D volume. |
| 17 | - **Range-frame axes (B2).** Axis lines span exactly the data range, ends |
| 18 | labelled; no outward padding. Exception: keep zero baseline for bar value axes. |
| 19 | - **Direct labels (B4).** Label lines at their endpoints and bars beside the bars; |
| 20 | do not emit a separate legend. |
| 21 | - **Minimal ink (B5).** White background, no gridlines (or faint hairlines at |
| 22 | major ticks only), no border box, no 3-D, no shadows, no gradients. |
| 23 | - **One encoding per datum (B6).** Don't restate a value with fill + border + |
| 24 | label; keep the strongest channel. |
| 25 | - **Many series → small multiples (B3).** Repeat one shrunken design with shared |
| 26 | scales rather than overplotting; order frames meaningfully. |
| 27 | - **Money over time (B7).** If the series is currency across years, convert to |
| 28 | real terms first (use `assess-graphical-excellence/scripts/deflate.py` with |
| 29 | retrieved CPI) and label the axis "real <base-year> <currency>". |
| 30 | - **Inert SVG (B8).** No `<script>`, no event-handler attributes (`onload=` |
| 31 | etc.), no `javascript:` URLs, no `<foreignObject>`, no SMIL `<animate>`/ |
| 32 | `<set>`. The four scripts above never emit these; if you hand-build SVG for |
| 33 | one of the "other genres" below, keep it inert too — `wrap_html.py` will |
| 34 | refuse to wrap anything carrying active content. |
| 35 | |
| 36 | ## Pick the genre first |
| 37 | |
| 38 | Before writing any code, check `references/tufte-principles.md` Part C and |
| 39 | choose Tufte's genre that fits the data shape. The toolkit ships a working |
| 40 | script for the four genres below; for the rest, build the SVG by hand |
| 41 | following Part C's recipe. |
| 42 | |
| 43 | | Data shape | Tufte genre (Part C) | Script | |
| 44 | |---|---|---| |
| 45 | | 1 × N over time | C10 time-series | `render_line_svg.py` | |
| 46 | | K series indexed by a category | C5 small multiples | `small_multiples.py` | |
| 47 | | Distributions of K groups | C1 quartile plot | `quartile_plot.py` | |
| 48 | | Bivariate / scatter | C2 range frame (+ optional C3 dot-dash marginals) | `range_frame.py` | |
| 49 | | Categorical 1-value | C9 white-grid bar / C6 supertable | hand-build SVG | |
| 50 | | ≤20 numbers total | C6 supertable | hand-build HTML/SVG | |
| 51 | | Many variables, geographic | F (Minard / Snow / Cancer Atlas) | hand-build SVG | |
| 52 | |
| 53 | If the data is ≤20 numbers, **prefer a table** (VDQI p.56) — say so to the user |
| 54 | before rendering. If forced to graph, use C6 supertable, not pie/bar. |
| 55 | |
| 56 | ## Multi-render rule (when to produce N alternatives) |
| 57 | |
| 58 | When the assess step flagged a "Multi-render trigger" (data shape with more |
| 59 | than one correct Tufte answer), **render both alternatives side-by-side** in |
| 60 | one output, then let the user pick. This is the discipline that prevents |
| 61 | quiet defaulting to the obvious genre. |
| 62 | |
| 63 | | Data shape | Render BOTH | |
| 64 | |---|---| |
| 65 | | 1 number / single ratio | (a) a one-sentence prose statement with the number inline, and (b) a tiny inline visual — a one-row proportion bar or a small two-color dot square | |
| 66 | | ≤20 numbers | (a) a C6 supertable (with optional table-graphic sparkline column), and (b) a single Tufte chart (C8 + C2 range-frame dot plot, or C9 white-grid bar) | |
| 67 | | Many series of one x | (a) a C5 small-multiples grid, and (b) an overplotted line/dot chart with direct end-labels | |
| 68 | | Distributions across groups | (a) a C1 quartile plot, and (b) a strip plot or histogram | |
| 69 | | Bivariate scatter | (a) a C2 range frame, and (b) the C3 dot-dash variant adding marginals | |
| 70 | |
| 71 | For all other data shapes, render one canonical Tufte form (don't manufacture |
| 72 | alternatives when one obviously dominates). When emitting both alternatives, |
| 73 | state in one sentence why each exists and what to look at in each — the goal |
| 74 | is letting the reader see the toolkit's range, not flooding them with |
| 75 | near-duplicates. |
| 76 | |
| 77 | ## How to render |
| 78 | |
| 79 | ### Line / time-series (C10) |
| 80 | |
| 81 | ``` |
| 82 | python scripts/render_line_svg.py \ |
| 83 | --data '[{"x":2000,"y":12.1},{"x":2023,"y":22.9}]' \ |
| 84 | --title "Revenue (real 2023 USD, M)" --series "Revenue" --out |