Skip to main content

Chart Style Book

This is the canonical reference for how a "good" Blocklens chart looks. It distills the rules expressed across the ~70 built-in templates (chart-templates), the DB-seeded templates (template-seeds), and the rendering behaviour of the chart renderer.

Two audiences:

  1. Frontend / template authors — apply these rules when designing new templates or seed data.
  2. The Buddy lab-author skill — loads a condensed copy of these rules so AI-generated chart configs match the visual language of the curated catalog.

If a rule below contradicts what a template currently does, the template wins for legacy reasons but new work should follow this guide.


1. Axes​

1.1 Price always on the right Y-axis, log scale​

Every template that overlays BTC price puts price on a separate right axis with scale: "log". The primary metric goes on the left axis. Linking price visually but separating it numerically keeps fast-growing metrics (holdings, AUM, supply) and price both readable.

Exception: price-model templates (realized_price, transferred_price) put price on the same axis as the model so users can compare two prices on one scale. That axis is log.

1.2 Metrics that share a unit share an axis​

  • lth_supply + sth_supply → one left axis (both BTC).
  • lth_mvrv + sth_mvrv → one left axis (both ratios).
  • Funding rate (%) on left, price (USD) on right — different units, different axes.

Do not stack two metrics with very different ranges on the same linear axis. The smaller series will be visually crushed.

1.3 Cycle comparison: allLeftAxis: true + log scale​

Cycle-performance templates (e.g. cycle-perf-low, cycle-perf-ath, cycle-perf-halving) force allLeftAxis: true with yAxes: [{ side: "left", scale: "log" }]. Multiple cycles overlay on the same log axis so exponential growth is comparable across cycles.

1.4 Axis label color​

  • Multiple differently-colored series sharing an axis → axis label is neutral dark gray #6b7280.
  • Single metric owning an axis → label may inherit that metric's color (used rarely; gray is the safe default).

2. Scale selection​

Use linearUse log
Supplies, flows, counts, USD values, market capBTC price (always)
Ratios (MVRV, SOPR), percentages, funding ratesEquity indices when shown alone (SPY/QQQ/IWM)
Daily deltas, net changesPrice models (realized_price, transferred_price)
Anything that fluctuates in a bounded rangeCycle-performance overlays

If a metric spans more than ~2 orders of magnitude over the visible window, prefer log.


3. Chart type per metric​

StyleWhenfillOpacity
areaHoldings, supplies, cumulative flows, AUM, stacked cohorts0.3
lineRatios (MVRV, SOPR), rates (funding), price overlays0.1
barDaily / 30-day deltas (net flows, supply 30D Δ)0.5
candlestickOHLC only—

Bars on a log axis render unreadably — keep bars linear.


4. Color palette​

4.1 By domain​

DomainElementHex
Price overlayBTC price (neutral)#6b7280
SupplyLTH#2563eb
SupplySTH#dc2626
Supplythird cohort#fbbf24 / #FFBB28
ETFholdings#f59e0b
ETFAUM#3b82f6
ETFnet flow#10b981
ETFcumulative flow#059669
ETFdominance#8b5cf6
ETFUS holdings#3b82f6
ETFrealized price#dc2626 / #ea580c
DATtotal holdings#F7931A (Bitcoin brand)
DATpublic companies#0088FE
DATgovernments#FF4444
DATprivate companies#FFBB28
DATnet change#82ca9d
DATdominance#ff7300
Fundingrate#3b82f6
Fundingspread#f97316
Fundingoverheated zonergba(239,68,68,0.1)
Fundingcapitulation zonergba(34,197,94,0.1)
Macroyields (neutral)#6b7280
Macroinflation#f59e0b
MacroVIX / risk#ef4444
Cycle 1→4violet → blue → green → orange#8b5cf6 → #3b82f6 → #22c55e → #f97316

4.2 Multi-metric coloring​

When two related cohorts appear together (LTH/STH, US/non-US, public/private), use the established pair (blue + red for LTH/STH) so users learn one mental model. Do not reinvent palettes per chart.


5. Reference lines and areas​

Use these only when the threshold has a clear interpretation:

  • Funding rate > 20% → red shaded area, label "Overheated".
  • Funding rate < -10% → green shaded area, label "Capitulation".
  • SOPR = 1.0 → reference line at break-even (opportunity — not yet in templates).
  • MVRV = 1.0 → reference line at fair-value parity.

Avoid decorative lines without numeric meaning. One or two annotations per chart maximum.

"referenceAreas": [
{ "y1": 20, "y2": 100, "yAxisId": "axis-left", "fill": "rgba(239,68,68,0.1)", "label": "Overheated" },
{ "y1": -100, "y2": -10, "yAxisId": "axis-left", "fill": "rgba(34,197,94,0.1)", "label": "Capitulation" }
]

6. Date range defaults​

PresetWhen
ALLLong-trend metrics — supply, valuation, cycle overlays, anything with a long history users want to see in full.
1YNoisy metrics where multi-year views obscure the signal — daily net flows, DAT net change, funding spread.
1W / 1MNever default — these are user-driven zoom levels.

7. Styling constants​

Use these everywhere unless there is a chart-specific reason not to:

  • strokeWidth: 2 for all lines.
  • fillOpacity: 0.3 for areas, 0.1 for line overlays, 0.5 for bars.
  • instanceId format: {metricId}-{seq} (e.g. price-1, lth_supply-1). Stable across edits.

8. Naming conventions​

  • Chart name — noun phrase, title-cased like a column header. ≤60 characters. No emojis.
    • Good: BTC LTH Supply with 30D Change, ETF Net Flow vs. Price
    • Bad: 📈 Bitcoin Long-Term Holders!!! (2024)
  • Description — one sentence describing what the chart shows and the insight it surfaces.
    • Good: Long-term holder supply with 30-day delta bars to surface accumulation and distribution waves.

9. Anti-patterns​

Reject these when reviewing a template or AI-generated config:

  • Single metric on a two-axis layout — the empty axis is wasted real estate.
  • More than 4 series on one axis without stacking — visually unreadable.
  • Mixing very different ranges on a single linear axis (e.g. counts 1-2M with price 20k-100k) — the smaller series is invisible.
  • BTC price on a linear axis when paired with a high-growth metric — price will dominate visually.
  • Decorative reference lines without a numeric meaning — adds noise, not signal.
  • Bars on a log scale — render unreadably.
  • Per-chart bespoke palette — breaks the user's learned color associations.

10. Quick reference for the lab-author skill​

When Buddy builds a chart config from natural language:

  1. Resolve the metric IDs via search_metrics / get_metric. Confirm each metric's grade is within the user's tier.
  2. Decide axis layout:
    • Is BTC price one of the series? → price on right with scale: "log", primary on left.
    • Do remaining metrics share a unit? → same axis. Different units? → different axes.
  3. Pick chart styles per §3.
  4. Pick colors per §4.1 — use the established palette for the domain.
  5. Pick scale per §2.
  6. Pick date range per §6.
  7. Add reference lines/areas only when the threshold has a clear meaning (§5).
  8. Apply styling constants (§7) and naming conventions (§8).
  9. Run the anti-pattern checklist (§9) before saving.

11. Where the templates live​

  • Static (frontend) — chart-templates. ~70 templates loaded at app start.
  • Database (templates table) — curated templates seeded by template-seeds and surfaced via the templates API.
  • User-created (user-charts table) — what the rest of this guide is about. Saved via the Lab UI, by forking a template, or by Buddy's save_chart MCP tool.

The config JSON shape (metrics, formulas, yAxes, dateRange, referenceLines, referenceAreas, etc.) is identical across all three sources — see the chart config in the Lab API client for the TypeScript definition and the API schema for the Pydantic schema.