Observable Plot

Modern data visualization library developed by Observable HQ. Based on Grammar of Graphics concept, enabling declarative chart creation. Inherits D3.js philosophy while achieving more concise notation.

JavaScriptData VisualizationStatic Site GeneratorDashboardInteractiveD3.jsReactive

Framework

Observable Framework

Overview

Observable Framework is a static site generator for creating data applications, dashboards, and reports.

Details

Observable Framework is a specialized static site generator developed by Observable for building data-driven applications. It combines frontend JavaScript with any backend language to create high-performance data visualizations and interactive dashboards. The framework adopts a reactive programming model where visualizations automatically update in response to data changes. Data loaders generate static snapshots of data at build time, enabling instantly loading dashboards. It integrates with powerful libraries including Observable Plot, D3.js, Mosaic, Vega-Lite, and DuckDB, supporting large-scale data processing and visualization. With an intuitive approach combining Markdown and JavaScript, it can be used for everything from technical reports to complex business intelligence applications. The framework features live preview capabilities, polyglot support for multiple programming languages, and enterprise-grade deployment options through Observable Cloud.

Pros and Cons

Pros

  • High Performance: Instant-loading dashboards through pre-computed data loaders
  • Reactive: Spreadsheet-like automatic updates with simple state management
  • Polyglot Support: Support for JavaScript, Python, R, SQL, and more
  • Rich Visualization Libraries: Integration with Plot, D3, Vega-Lite, Mermaid, and many others
  • Large Data Support: Efficient client-side data processing with DuckDB
  • Intuitive Syntax: Easy development with Markdown and JavaScript combination
  • Live Preview: Real-time change preview with hot reload capabilities

Cons

  • Learning Curve: Need to learn Observable's unique reactive model
  • Static Site Constraints: Data is fixed at build time, limiting real-time data capabilities
  • Ecosystem: Relatively smaller community compared to React/Vue
  • Customization Limits: Dependency on specific architecture limits flexibility
  • Debugging Complexity: Reactive dependencies can be difficult to debug

Key Links

Code Examples

Hello World

// Basic Markdown + JavaScript syntax
display("Hello, Observable Framework!");

// Creating HTML elements
display(html`<h1>Welcome to Observable!</h1>`);

// Simple data display
const data = [1, 2, 3, 4, 5];
display(data);

Basic Data Visualization

// Data preparation
const salesData = [
  {month: "Jan", sales: 120},
  {month: "Feb", sales: 180},
  {month: "Mar", sales: 150},
  {month: "Apr", sales: 220},
  {month: "May", sales: 190}
];

// Basic bar chart using Observable Plot
Plot.plot({
  marks: [
    Plot.barY(salesData, {x: "month", y: "sales", fill: "steelblue"}),
    Plot.ruleY([0])
  ],
  y: {grid: true, label: "Sales ($1000)"},
  title: "Monthly Sales"
})

Interactive Chart

// Data loading
const data = FileAttachment("sales.csv").csv({typed: true});

// Interactive selection
const selectedCategory = view(
  Inputs.select(
    Array.from(new Set(data.map(d => d.category))), 
    {label: "Select Category"}
  )
);

// Dynamic chart based on selection
Plot.plot({
  marks: [
    Plot.dot(
      data.filter(d => d.category === selectedCategory),
      {x: "date", y: "value", fill: "red", r: 4}
    ),
    Plot.lineY(
      data.filter(d => d.category === selectedCategory),
      {x: "date", y: "value", stroke: "blue"}
    )
  ],
  x: {type: "utc", label: "Date"},
  y: {grid: true, label: "Value"},
  title: `${selectedCategory} Trend`
})

Data Loading and Processing

// Loading CSV files
const customers = FileAttachment("customers.csv").csv({typed: true});

// Loading JSON data
const config = FileAttachment("config.json").json();

// Getting data from data loaders
const processedData = FileAttachment("data/processed.json").json();

// Data processing and display
const summary = customers.then(data => 
  d3.rollup(
    data,
    v => v.length,
    d => d.region
  )
);

// Table format display
Inputs.table(customers, {
  columns: ["name", "region", "revenue"],
  sort: "revenue",
  reverse: true
})

Animation

// Time-based animation
const currentTime = Generators.now(1000); // Update every second

// Animated data
const animatedData = Array.from({length: 10}, (_, i) => ({
  x: i,
  y: Math.sin((currentTime / 1000 + i) * 0.5) * 50 + 100
}));

// Animated chart
Plot.plot({
  marks: [
    Plot.lineY(animatedData, {x: "x", y: "y", stroke: "blue", strokeWidth: 3}),
    Plot.dot(animatedData, {x: "x", y: "y", fill: "red", r: 4})
  ],
  y: {domain: [50, 150]},
  title: "Real-time Animation"
})

Multiple View Coordination

// Shared data
const salesData = FileAttachment("monthly-sales.csv").csv({typed: true});

// Region selection component
const selectedRegion = view(
  Inputs.select(
    ["All", ...Array.from(new Set(salesData.map(d => d.region)))],
    {label: "Select Region", value: "All"}
  )
);

// Filtered data
const filteredData = selectedRegion === "All" 
  ? salesData 
  : salesData.filter(d => d.region === selectedRegion);

// Trend chart
const trendChart = Plot.plot({
  marks: [
    Plot.lineY(filteredData, {x: "month", y: "sales", stroke: "blue"}),
    Plot.dot(filteredData, {x: "month", y: "sales", fill: "red"})
  ],
  title: `${selectedRegion} Sales Trend`
});

// Summary table
const summaryTable = Inputs.table(
  d3.rollup(
    filteredData,
    v => ({
      totalSales: d3.sum(v, d => d.sales),
      avgSales: d3.mean(v, d => d.sales),
      count: v.length
    }),
    d => d.product
  ),
  {columns: ["product", "totalSales", "avgSales", "count"]}
);

// Layout
html`
  <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;">
    <div>${trendChart}</div>
    <div>${summaryTable}</div>
  </div>
`