Skip to content

Parametric TickFormat - Custom Axis Formatting

Observable Plot's tickFormat option accepts both d3-format strings and JavaScript functions for customizing axis tick labels. The dg-plot plugin supports parametric functions as strings, allowing complex formatting logic directly in your configuration.

Overview

Use parametric tickFormat to:

  • Convert decimals to percentages
  • Format currency values
  • Abbreviate large numbers (K, M, B)
  • Apply conditional formatting
  • Create custom date/time labels
  • Round or truncate values

Basic Usage

Percentage Formatting

Convert decimal values (0-1) to percentages (0-100%):

data-glass
type: line
data:
  source: '[{"year": 2020, "adoption": 0.25}, {"year": 2021, "adoption": 0.42}, {"year": 2022, "adoption": 0.68}]'
x: year
y: adoption
scales:
  y:
    tickFormat: '(d) => Math.round(d * 100) + "%"'

Result: Y-axis shows "25%", "42%", "68%"

Currency Formatting

Format numbers as USD currency:

data-glass
type: bar
data:
  source: '[{"quarter": "Q1", "revenue": 125000}, {"quarter": "Q2", "revenue": 187500}, {"quarter": "Q3", "revenue": 234000}]'
x: quarter
y: revenue
scales:
  y:
    tickFormat: '(d) => "$" + d.toLocaleString()'

Result: Y-axis shows "$125,000", "$187,500", "$234,000"

Number Abbreviation

Abbreviate large numbers with K (thousands) and M (millions):

data-glass
type: bar
data:
  source: '[{"company": "A", "users": 1500}, {"company": "B", "users": 425000}, {"company": "C", "users": 2300000}]'
x: company
y: users
scales:
  y:
    tickFormat: |
      (d) => {
        if (d >= 1e6) return (d / 1e6).toFixed(1) + "M";
        if (d >= 1e3) return (d / 1e3).toFixed(1) + "K";
        return d;
      }

Result: Y-axis shows "1.5K", "425.0K", "2.3M"

Template Literals

Use template literals for flexible formatting:

data-glass
type: scatter
data:
  source: '[{"day": 1, "temp": 72}, {"day": 2, "temp": 68}, {"day": 3, "temp": 75}]'
x: day
y: temp
scales:
  y:
    tickFormat: '(d) => `${d}°F`'

Result: Y-axis shows "72°F", "68°F", "75°F"

Conditional Formatting

Apply different formats based on value ranges:

data-glass
type: area
data:
  source: '[{"month": "Jan", "sales": 45}, {"month": "Feb", "sales": 120}, {"month": "Mar", "sales": 80}]'
x: month
y: sales
scales:
  y:
    tickFormat: '(d) => d > 100 ? "High: $" + d : "Low: $" + d'

Result: Y-axis shows "Low: $45", "High: $120", "Low: $80"

Real-World Examples

1. Market Share Dashboard

Display percentages for market share analysis:

data-glass
type: bar
data:
  source: '[{"vendor": "A", "share": 0.32}, {"vendor": "B", "share": 0.28}, {"vendor": "C", "share": 0.25}, {"vendor": "D", "share": 0.15}]'
x: vendor
y: share
scales:
  y:
    domain: [0, 1]
    tickFormat: '(d) => (d * 100).toFixed(0) + "%"'

2. Financial Data with Currency

Format stock prices with proper locale and precision:

data-glass
type: line
data:
  source: '[{"date": "2024-01", "price": 124.50}, {"date": "2024-02", "price": 128.75}, {"date": "2024-03", "price": 132.10}]'
x: date
y: price
scales:
  y:
    tickFormat: '(d) => "$" + d.toFixed(2)'

3. Large-Scale Analytics

Handle millions of records with abbreviated notation:

data-glass
type: bar
data:
  source: '[{"region": "US", "impressions": 5400000}, {"region": "EU", "impressions": 2100000}, {"region": "APAC", "impressions": 890000}]'
x: region
y: impressions
scales:
  y:
    tickFormat: |
      (d) => {
        if (d >= 1e9) return (d / 1e9).toFixed(1) + "B";
        if (d >= 1e6) return (d / 1e6).toFixed(1) + "M";
        if (d >= 1e3) return (d / 1e3).toFixed(1) + "K";
        return d.toFixed(0);
      }

4. Survey Results with Precision

Display survey scores with decimal precision:

data-glass
type: area
data:
  source: '[{"q": "Q1", "score": 3.42}, {"q": "Q2", "score": 3.87}, {"q": "Q3", "score": 4.12}]'
x: q
y: score
scales:
  y:
    domain: [0, 5]
    tickFormat: '(d) => d.toFixed(1)'

5. Time-Based Metrics

Format duration/time values:

data-glass
type: line
data:
  source: '[{"day": 1, "duration": 245}, {"day": 2, "duration": 312}, {"day": 3, "duration": 189}]'
x: day
y: duration
scales:
  y:
    tickFormat: |
      (d) => {
        const mins = Math.floor(d / 60);
        const secs = d % 60;
        return mins + "m " + secs + "s";
      }

Result: Y-axis shows "4m 5s", "5m 12s", "3m 9s"

Advanced Patterns

Locale-Aware Formatting

Use native JavaScript locale features:

yaml
scales:
  y:
    tickFormat: |
      (d) => new Intl.NumberFormat("en-US", {
        style: "currency",
        currency: "USD",
        minimumFractionDigits: 0
      }).format(d)

Scientific Notation

For very large or very small numbers:

yaml
scales:
  y:
    tickFormat: '(d) => d.toExponential(2)'

Conditional with Threshold

Highlight values above/below thresholds:

yaml
scales:
  y:
    tickFormat: |
      (d) => {
        if (d > 1000) return "🔴 " + d.toFixed(0);
        if (d > 500) return "🟡 " + d.toFixed(0);
        return "🟢 " + d.toFixed(0);
      }

Function Syntax Guide

Single-Line Expressions

yaml
tickFormat: '(d) => d * 100 + "%"'
tickFormat: '(d) => "$" + d.toLocaleString()'

Template Literals

yaml
tickFormat: '(d) => `${d.toFixed(2)}x`'
tickFormat: '(d) => `Value: ${d > 100 ? "high" : "low"}`'

Multi-Line Functions

yaml
tickFormat: |
  (d) => {
    if (d >= 1e6) return (d / 1e6).toFixed(1) + "M";
    if (d >= 1e3) return (d / 1e3).toFixed(1) + "K";
    return d.toFixed(0);
  }

Without Parentheses

For single parameters, parentheses are optional:

yaml
tickFormat: 'd => d * 2'

Security Considerations

  • No Global Scope Access: Functions are evaluated in isolation without access to outer scope variables
  • Safe Evaluation: Uses JavaScript Function constructor (not eval)
  • No Side Effects: Formatters should only return formatted strings, not modify data
  • Validation: Invalid functions fall back to default formatting with a warning

Performance Tips

  1. Keep Formatting Simple: Complex calculations are executed for every tick label
  2. Cache Computed Values: Pre-compute constants outside the function body
  3. Avoid Loops: For range-based formatting, use conditional logic instead
  4. Test Edge Cases: Ensure formatters handle minimum/maximum axis values

Combining with Other Scale Options

Parametric tickFormat works alongside other scale configuration:

yaml
scales:
  y:
    type: linear
    domain: [0, 100]
    nice: true
    tickFormat: '(d) => d + "%"'
    label: "Completion Rate"

Fallback to d3-Format Strings

If you prefer d3-format syntax, it's still supported:

yaml
scales:
  y:
    tickFormat: ',d'  # Thousand separators

The plugin automatically detects arrow functions vs. d3-format strings.

Common Pitfalls

  1. Forgetting Return: Multi-line functions must have explicit return

    yaml
    # ❌ Wrong
    tickFormat: '(d) => { d * 100 }'
    
    # ✅ Correct
    tickFormat: '(d) => { return d * 100; }'
  2. Unescaped Template Literals: Quote properly in YAML

    yaml
    # ❌ Wrong
    tickFormat: (d) => `${d}%`
    
    # ✅ Correct
    tickFormat: '(d) => `${d}%`'
  3. Assuming Data Types: Verify input data types before formatting

    yaml
    # ✅ Safe - handles null/undefined
    tickFormat: '(d) => d != null ? d.toFixed(2) : "N/A"'

Released under the MIT License. Built by Boundary Lab.