# ============================================================
# Swivel Charts — Rules Registry
# ============================================================
# Machine-readable extract of the chart guidelines documented
# visually in design-system/Charts.html. That HTML page remains
# the rendered showcase; this file is the queryable source of
# truth for linters, codegen, and review agents.
#
# Schema:
#   universal:        rules every chart inherits
#   by_chart_type:    per-shape rules and palette anchors
#   selection:        which chart to reach for, by job
#   do_dont:          the big five off-brand patterns
#
# Tokens referenced (e.g. --viz-slate, --surface-base) are
# defined in design-system/tokens.css.
# ============================================================

version: 1
canonical_source: design-system/Charts.html
# Component-level rules (which chart to use, donut swap triggers, the metric
# type token, ChartFormat for axes) live in design-system/adherence.rules.yaml.
# This file is the chart-craft registry: axis formatting, palettes, tooltips.
related: design-system/adherence.rules.yaml

# ────────────────────────────────────────────────────────────
# Universal rules — every chart inherits these.
# ────────────────────────────────────────────────────────────
universal:

  axis_labels:
    summary: >-
      Tickmark labels read in passing. They must fit in a narrow
      gutter without truncating, and the unit must be unambiguous
      at a glance.
    rules:
      - id: currency_on_every_tick
        severity: error
        summary: If the metric is money, every tick label carries the currency symbol ($, €, £, etc.).
        why: The axis title alone is not enough; readers don't scan top-down.
        bad: ["16000", "12000", "8000", "0"]
        good: ["$16K", "$12K", "$8K", "$0"]

      - id: abbreviate_by_magnitude
        severity: error
        summary: Abbreviate magnitudes so labels never wrap.
        mapping:
          thousands: K        # 1,000        → 1K
          millions:  MM       # 1,000,000    → 1MM
          billions:  B        # 1,000,000,000 → 1B
        notes:
          - Use MM (not M) for millions in finance contexts to avoid confusion with thousands in roman-numeral notation.
          - Switch to M only if the surrounding product uses it consistently.

      - id: one_suffix_per_axis
        severity: error
        summary: Don't mix $500, $1.5K, $2K on the same scale.
        rule: Snap the whole axis to the largest tick's unit so labels line up in length.

      - id: precision_follows_range
        severity: warn
        summary: Never write more decimals than the data resolves.
        examples:
          - range: '"0–10MM",  precision: "$2.5MM"'
          - range: '"0–100MM", precision: "$25MM"'

      - id: non_currency_metrics_still_abbreviate
        severity: warn
        summary: Non-currency metrics drop the $ but keep K / MM / B (e.g. "1.2K impressions", "8MM bids"). Percentages keep % on every tick.

      - id: negative_sign_placement
        severity: error
        summary: Currency symbol goes inside the negative sign.
        good: "−$2.4K"
        bad:  "$−2.4K"
        applies_to: [divergent-bars, waterfall-charts]

      - id: tooltips_show_full_precision
        severity: info
        summary: Axes abbreviate for scanning. Tooltips and bar end-labels (when there's room) show exact figures like $16,482.30.

  tooltips:
    rules:
      - id: tooltip_fill_and_stroke
        summary: Tooltip plate is always --surface-base with a 1px --surface-stroke hairline. Never invert per theme — tokens adapt.
      - id: tooltip_title_format
        summary: Title uses UI/Default (Antarctica 14px regular). Full date + time, no abbreviations. Top, left-aligned, no underline.
      - id: tooltip_series_rows
        summary: Series name in UI/Default muted (--graphite-bg). Value in UI/Small (Avantt SemiBold 12px) right-aligned with tabular numerals. Swatch is a 16×3 rounded line, matching the line/area-fill the row describes.
      - id: tooltip_active_row
        summary: The series under the cursor promotes to Avantt SemiBold + --graphite-90. Only one row at a time. Never reorder rows on hover — visual stability beats novelty.
      - id: tooltip_other_row
        summary: An "Other" row, if shown, sits last with swatch in --graphite-20. Never use a series color for "Other".

# ────────────────────────────────────────────────────────────
# Per-chart-type rules.
# ────────────────────────────────────────────────────────────
by_chart_type:

  line:
    use_for: continuous metric over time
    default_examples: [fill-rate, CPM, bid-density, ROI]
    palette_order:
      primary:    var(--viz-slate)
      comparator: var(--viz-switch)
      additional: [var(--viz-deep), var(--viz-steel), var(--viz-mist), var(--viz-graphite)]
    rules:
      - id: order_by_importance
        summary: Order series by importance, not alphabetically. Primary → slate, comparator → switch, then deep, steel, mist, graphite.
      - id: handle_missing_data
        summary: Connect through missing data with a dashed segment. Never drop to zero on null.
      - id: no_markers_under_200px
        summary: No markers on lines narrower than 200px — they crowd the path. Use markers on small multiples and sparklines instead.
      - id: above_six_series
        summary: Above six lines, switch to small multiples instead of stacking lines.

  area:
    use_for: magnitude over time (total matters more than rate-of-change)
    single_series_fill_opacity: 0.10 - 0.15
    baseline: zero
    rules:
      - id: stay_in_cool_palette
        summary: Keep stacked-area to the slate/switch family. Never mix in semantic colors — those mean "good/bad," not "category."

  bar:
    use_for: categorical comparison
    variants: [vertical, horizontal, grouped, stacked]
    rules:
      - id: sort_by_value
        summary: Always sort bars by value unless time order is the point.
      - id: pick_orientation_by_intent
        rules:
          - vertical:   "over time when the gap between points matters"
          - horizontal: "ranked categories — default for 'top N'"
          - grouped:    "split comparison (this period vs prior)"
          - stacked:    "part-of-whole when the total is also interesting"

  kpi_card:
    use_for: single dominant metric on Command surfaces
    rules:
      - id: one_number_one_label_one_delta
        summary: One number, one label, one delta. If you need a second dimension, the answer is a chart, not a bigger card.
      - id: dont_bury_the_delta
        summary: Arrow + value next to the metric, in --font-mono 11px 500. Color from --success (up) / --error (down) / --graphite-bg (neutral).
      - id: label_says_window
        summary: The label states what window the delta covers ("vs. last 7d") — not the card chrome.

  donut:
    use_for: one anchor number with ≤ 4 supporting slices
    rules:
      - id: needs_an_anchor
        summary: Donut center must hold a single dominant fact (percent, count). Without an anchor, use a horizontal bar.
      - id: max_slices
        summary: Past 4 slices, switch to horizontal bar — humans rank lengths, not arcs.
      - id: never_pie
        summary: Never use a pie chart with no center. The donut's center is what justifies the format.
    swap_triggers:
      - condition: '"5+ slices",              fallback: horizontal-bar'
      - condition: '"values within 5%",       fallback: horizontal-bar'
      - condition: '"no anchor number",       fallback: horizontal-bar'
      - condition: '"trend instead of snapshot", fallback: line'

  heatmap:
    use_for: two categorical dimensions, one magnitude
    palette: sequential   # --seq-slate-1…7 or --seq-switch-1…7 or --seq-warm-1…7
    rules:
      - id: density_matters
        summary: Give a heatmap enough rows × columns to reveal pattern, not so many cells become noise.
      - id: legend_required
        summary: Pair with a one-line color legend below (0% → 100%).

  scatter:
    use_for: correlation or outliers across two continuous dimensions
    third_dimension: encode-as-size

  bullet:
    use_for: actual vs. target with qualitative range
    replaces: gauges

  states:
    documented: [loading, empty, no-data, partial, error]

  annotations:
    documented: [target-lines, event-markers, range-bands, callouts]

  small_multiples:
    use_for: ">3 series that would otherwise stack on one chart"

# ────────────────────────────────────────────────────────────
# Selection cheat sheet — pick a chart by job-to-be-done.
# ────────────────────────────────────────────────────────────
selection:
  - chart: line
    when: continuous metric over time
    examples: [fill-rate, CPM, ROI]
  - chart: area
    when: magnitude over time; total matters more than rate-of-change
  - chart: bar-vertical
    when: value over time when the gap between points matters
  - chart: bar-horizontal
    when: ranked categories ("top N")
  - chart: bar-stacked
    when: part-of-whole when the total is also interesting
  - chart: bar-divergent
    when: plus/minus vs. a baseline (use diverging palette)
  - chart: sparkline
    when: inline context for a single metric (no axes)
  - chart: donut
    when: one anchor number with ≤4 supporting slices
  - chart: heatmap
    when: two categorical dimensions, one magnitude
  - chart: scatter
    when: correlation or outliers across two continuous dimensions
  - chart: bullet
    when: actual vs. target with qualitative bands (replaces gauges)
  - chart: small-multiples
    when: more than three series — always

# ────────────────────────────────────────────────────────────
# Do / don't — the five biggest off-brand patterns.
# These are full-fidelity in Charts.html §13; summarized here.
# ────────────────────────────────────────────────────────────
do_dont:
  - dont: Pie charts. Donut with an anchor number, or a horizontal bar.
  - dont: Truncating the y-axis on a bar chart. Bars compare lengths from zero.
  - dont: Stacked bars with > 6 slices. Switch to small multiples.
  - dont: Multi-color icon swatches in legends. Use the line/area shape that matches the plot.
  - dont: Reordering legend or tooltip rows on hover. Visual stability beats novelty.
