Observable Notebooks Samples

Comprehensive Observable Notebook examples including data analysis, visualization workflows, and advanced interactive techniques

Key Facts

Category
Data Visualization
Items
3
Format Families
text

Sample Overview

Comprehensive Observable Notebook examples including data analysis, visualization workflows, and advanced interactive techniques This sample set belongs to Data Visualization and can be used to test related workflows inside Elysia Tools.

💻 Observable Basic Notebook Creation text

🟢 simple

Learn to create Observable Notebooks using cells, visualizations, and interactive components

// Observable Notebook - Basic Tutorial Example
// Copy directly to Observable platform for use

// 1. Basic Data Definition
salesData = {
  const data = [
    { month: 'January', revenue: 12000, profit: 3000, customers: 150 },
    { month: 'February', revenue: 15000, profit: 4500, customers: 200 },
    { month: 'March', revenue: 18000, profit: 5400, customers: 250 },
    { month: 'April', revenue: 16000, profit: 4800, customers: 230 },
    { month: 'May', revenue: 20000, profit: 6000, customers: 280 },
    { month: 'June', revenue: 22000, profit: 6600, customers: 320 }
  ];

  return data;
}

// 2. Basic Chart - Using Plot.js
chart = {
  // Import required libraries
  const plot = require("@observablehq/plot");

  // Create line chart
  const chart = plot.plot({
    title: "Monthly Sales Trend",
    width: 800,
    height: 400,
    grid: true,
    x: {
      label: "Month"
    },
    y: {
      label: "Amount ($)",
      tickFormat: (d) => `$${d.toLocaleString()}`
    },
    marks: [
      // Revenue line
      plot.line(salesData, {
        x: "month",
        y: "revenue",
        stroke: "#4e79a7",
        strokeWidth: 3
      }),
      // Profit line
      plot.line(salesData, {
        x: "month",
        y: "profit",
        stroke: "#f28e2c",
        strokeWidth: 3
      }),
      // Data points
      plot.dot(salesData, {
        x: "month",
        y: "revenue",
        fill: "#4e79a7",
        r: 5
      }),
      plot.dot(salesData, {
        x: "month",
        y: "profit",
        fill: "#f28e2c",
        r: 5
      })
    ]
  });

  return chart;
}

// 3. Interactive Table
table = {
  // Create interactive table
  const table = html`
    <table style="border-collapse: collapse; width: 100%; font-family: Arial, sans-serif;">
      <thead>
        <tr style="background-color: #f2f2f2;">
          <th style="border: 1px solid #ddd; padding: 12px; text-align: left;">Month</th>
          <th style="border: 1px solid #ddd; padding: 12px; text-align: left;">Revenue</th>
          <th style="border: 1px solid #ddd; padding: 12px; text-align: left;">Profit</th>
          <th style="border: 1px solid #ddd; padding: 12px; text-align: left;">Customers</th>
        </tr>
      </thead>
      <tbody>
        ${salesData.map(row => html`
          <tr style="border-bottom: 1px solid #ddd;">
            <td style="border: 1px solid #ddd; padding: 12px;">${row.month}</td>
            <td style="border: 1px solid #ddd; padding: 12px;">$${row.revenue.toLocaleString()}</td>
            <td style="border: 1px solid #ddd; padding: 12px;">$${row.profit.toLocaleString()}</td>
            <td style="border: 1px solid #ddd; padding: 12px;">${row.customers}</td>
          </tr>
        `)}
      </tbody>
    </table>
  `;

  return table;
}

// 4. Statistics Calculation
statistics = {
  const totalRevenue = salesData.reduce((sum, d) => sum + d.revenue, 0);
  const totalProfit = salesData.reduce((sum, d) => sum + d.profit, 0);
  const totalCustomers = salesData.reduce((sum, d) => sum + d.customers, 0);
  const avgProfit = totalProfit / salesData.length;

  return html`
    <div style="padding: 20px; background: #f0f8ff; border-radius: 8px; font-family: Arial, sans-serif;">
      <h3 style="margin-top: 0; color: #333;">📊 Statistical Summary</h3>
      <p><strong>Total Revenue:</strong> $${totalRevenue.toLocaleString()}</p>
      <p><strong>Total Profit:</strong> $${totalProfit.toLocaleString()}</p>
      <p><strong>Total Customers:</strong> ${totalCustomers.toLocaleString()}</p>
      <p><strong>Average Monthly Profit:</strong> $${avgProfit.toLocaleString()}</p>
      <p><strong>Profit Margin:</strong> ${((totalProfit / totalRevenue) * 100).toFixed(1)}%</p>
    </div>
  `;
}

// 5. Bar Chart Comparison
barChart = {
  const plot = require("@observablehq/plot");

  const chart = plot.plot({
    title: "Revenue vs Profit Comparison",
    width: 800,
    height: 400,
    x: {
      domain: salesData.map(d => d.month)
    },
    y: {
      grid: true,
      label: "Amount ($)",
      tickFormat: (d) => `$${d.toLocaleString()}`
    },
    color: {
      domain: ["revenue", "profit"],
      range: ["#4e79a7", "#f28e2c"]
    },
    marks: [
      plot.barX(
        salesData.flatMap(d => [
          { month: d.month, type: "revenue", value: d.revenue },
          { month: d.month, type: "profit", value: d.profit }
        ]),
        {
          x: "value",
          y: "month",
          fill: "type",
          fx: "type"
        }
      )
    ]
  });

  return chart;
}

// 6. Pie Chart Analysis
pieChart = {
  const plot = require("@observablehq/plot");

  const pieData = salesData.map(d => ({
    category: d.month,
    value: d.revenue
  }));

  const chart = plot.plot({
    title: "Monthly Revenue Distribution",
    width: 500,
    height: 500,
    marks: [
      plot.arc(pieData, {
        fill: "category",
        r: "value",
        sort: { reduce: "sum", reverse: true },
        title: (d) => `${d.category}: $${d.value.toLocaleString()}`
      }),
      plot.text(pieData, {
        text: (d) => `${((d.value / pieData.reduce((sum, x) => sum + x.value, 0)) * 100).toFixed(1)}%`,
        fill: "white",
        filter: (d) => d.value > 15000
      })
    ]
  });

  return chart;
}

// 7. Interactive Controls
interactiveControls = {
  const Inputs = require("@observablehq/inputs");

  // Create interactive controls
  const monthSelector = Inputs.select(salesData.map(d => d.month), {
    label: "Select Month",
    value: salesData[0].month
  });

  const showCustomers = Inputs.checkbox(["Show Customers"], {
    value: ["Show Customers"]
  });

  return html`
    <div style="display: flex; gap: 20px; align-items: center;">
      ${monthSelector}
      ${showCustomers}
    </div>
  `;
}

// 8. Markdown Documentation
markdown = {
  // Use Markdown to create documentation
  const md = require("@observablehq/markdown");

  return md`
# Observable Notebook Basic Tutorial

## 📚 What is Observable?

Observable is a collaborative data visualization platform that allows you to:

- ✨ Create interactive data visualizations
- 📊 Use JavaScript for data analysis
- 🔗 Easily integrate with other notebooks and libraries
- 🌐 Share and publish your work

## 🎯 What does this Notebook contain?

This sample notebook demonstrates the following features:

### 1. Data Definition
- Define basic data structures
- Use JavaScript arrays and methods

### 2. Visualization Components
- Line charts showing trends
- Bar charts for comparisons
- Pie charts displaying distributions
- Interactive tables

### 3. Statistical Analysis
- Calculate sums and averages
- Ratio and percentage analysis
- Data summary display

### 4. Interactive Elements
- Selectors and checkboxes
- Responsive updates
- User input handling

## 🚀 Getting Started

1. **Copy Code**: Copy these cells to Observable
2. **Run Cells**: Each cell will automatically run and display results
3. **Modify Parameters**: Try modifying data and parameters
4. **Share Results**: Publish your notebook to share with others

## 💡 Tips

- Use `require()` to import external libraries
- Variable names are cell names and automatically shared
- Modifying any cell will update dependent results
- Use `html``...\` to create HTML content
  `;
}

// 9. Export Functionality
exportData = {
  // Export as CSV format
  const csvContent = [
    ["Month", "Revenue", "Profit", "Customers"],
    ...salesData.map(d => [d.month, d.revenue, d.profit, d.customers])
  ].map(row => row.join(",")).join("\n");

  return html`
    <div style="padding: 20px; background: #f0fff0; border-radius: 8px; font-family: Arial, sans-serif;">
      <h3 style="margin-top: 0;">📥 Data Export</h3>
      <button
        onclick="navigator.clipboard.writeText('${csvContent}')"
        style="padding: 10px 20px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px;"
      >
        📋 Copy CSV to Clipboard
      </button>
      <pre style="margin-top: 10px; padding: 10px; background: #f5f5f5; border-radius: 4px; overflow-x: auto; font-size: 12px;">${csvContent}</pre>
    </div>
  `;
}

💻 Observable Data Analysis Workflow text

🟡 intermediate

Build a complete data analysis workflow including data cleaning, transformation, analysis, and visualization

// Observable Notebook - Data Analysis Workflow Example
// Shows complete data processing workflow

// 1. Raw Data Acquisition
rawData = {
  // Simulated sales data from API
  const salesData = [
    { date: '2024-01-15', product: 'Laptop', category: 'Electronics', quantity: 5, unit_price: 1200, region: 'North', sales_rep: 'Alice' },
    { date: '2024-01-16', product: 'Mouse', category: 'Electronics', quantity: 20, unit_price: 25, region: 'South', sales_rep: 'Bob' },
    { date: '2024-01-17', product: 'Desk Chair', category: 'Furniture', quantity: 3, unit_price: 450, region: 'East', sales_rep: 'Charlie' },
    { date: '2024-01-18', product: 'Monitor', category: 'Electronics', quantity: 8, unit_price: 300, region: 'West', sales_rep: 'Alice' },
    { date: '2024-01-19', product: 'Keyboard', category: 'Electronics', quantity: 15, unit_price: 80, region: 'North', sales_rep: 'David' },
    { date: '2024-01-20', product: 'Desk Lamp', category: 'Furniture', quantity: 12, unit_price: 35, region: 'South', sales_rep: 'Bob' },
    { date: '2024-01-21', product: 'Webcam', category: 'Electronics', quantity: 10, unit_price: 60, region: 'East', sales_rep: 'Charlie' },
    { date: '2024-01-22', product: 'Office Desk', category: 'Furniture', quantity: 2, unit_price: 800, region: 'West', sales_rep: 'David' },
    { date: '2024-01-23', product: 'Headphones', category: 'Electronics', quantity: 25, unit_price: 45, region: 'North', sales_rep: 'Alice' },
    { date: '2024-01-24', product: 'Bookshelf', category: 'Furniture', quantity: 4, unit_price: 150, region: 'South', sales_rep: 'Bob' },
    { date: '2024-01-25', product: 'USB Hub', category: 'Electronics', quantity: 30, unit_price: 15, region: 'East', sales_rep: 'Charlie' },
    { date: '2024-01-26', product: 'Filing Cabinet', category: 'Furniture', quantity: 6, unit_price: 200, region: 'West', sales_rep: 'David' },
    { date: '2024-01-27', product: 'Tablet', category: 'Electronics', quantity: 7, unit_price: 500, region: 'North', sales_rep: 'Alice' },
    { date: '2024-01-28', product: 'Office Chair', category: 'Furniture', quantity: 5, unit_price: 350, region: 'South', sales_rep: 'Bob' },
    { date: '2024-01-29', product: 'Smartphone', category: 'Electronics', quantity: 12, unit_price: 800, region: 'East', sales_rep: 'Charlie' },
    { date: '2024-01-30', product: 'Coffee Table', category: 'Furniture', quantity: 3, unit_price: 250, region: 'West', sales_rep: 'David' }
  ];

  return salesData;
}

// 2. Data Cleaning and Validation
dataQuality = {
  const qualityReport = {
    totalRecords: rawData.length,
    missingValues: 0,
    duplicateRecords: 0,
    priceAnomalies: 0,
    quantityAnomalies: 0
  };

  // Check for missing values
  rawData.forEach(record => {
    Object.values(record).forEach(value => {
      if (value === null || value === undefined || value === '') {
        qualityReport.missingValues++;
      }
    });
  });

  // Check for duplicate records
  const uniqueRecords = new Set(rawData.map(r => JSON.stringify(r)));
  qualityReport.duplicateRecords = rawData.length - uniqueRecords.size;

  // Check for price anomalies
  const prices = rawData.map(r => r.unit_price);
  const priceStats = {
    mean: prices.reduce((a, b) => a + b, 0) / prices.length,
    std: Math.sqrt(prices.reduce((sq, n) => sq + Math.pow(n - (prices.reduce((a, b) => a + b, 0) / prices.length), 2), 0) / prices.length)
  };

  rawData.forEach(record => {
    if (Math.abs(record.unit_price - priceStats.mean) > 3 * priceStats.std) {
      qualityReport.priceAnomalies++;
    }
    if (record.quantity < 0) {
      qualityReport.quantityAnomalies++;
    }
  });

  return qualityReport;
}

// 3. Data Transformation and Feature Engineering
processedData = {
  const processed = rawData.map(record => ({
    ...record,
    // Calculate total sales
    total_sales: record.quantity * record.unit_price,
    // Extract month
    month: new Date(record.date).toLocaleString('en-US', { month: 'short' }),
    // Extract day of week
    day_of_week: new Date(record.date).toLocaleString('en-US', { weekday: 'short' }),
    // Price categorization
    price_category: record.unit_price < 50 ? 'Low' : record.unit_price < 500 ? 'Medium' : 'High',
    // Bulk order indicator
    bulk_order: record.quantity > 10
  }));

  return processed;
}

// 4. Descriptive Statistical Analysis
descriptiveStats = {
  const totalSales = processedData.reduce((sum, d) => sum + d.total_sales, 0);
  const avgOrderValue = totalSales / processedData.length;

  const topProducts = processedData.reduce((acc, d) => {
    acc[d.product] = (acc[d.product] || 0) + d.total_sales;
    return acc;
  }, {});

  const topRegions = processedData.reduce((acc, d) => {
    acc[d.region] = (acc[d.region] || 0) + d.total_sales;
    return acc;
  }, {});

  const categoryStats = processedData.reduce((acc, d) => {
    if (!acc[d.category]) {
      acc[d.category] = { count: 0, total: 0, avg: 0 };
    }
    acc[d.category].count++;
    acc[d.category].total += d.total_sales;
    acc[d.category].avg = acc[d.category].total / acc[d.category].count;
    return acc;
  }, {});

  return {
    overview: {
      totalSales,
      avgOrderValue,
      totalOrders: processedData.length,
      totalQuantity: processedData.reduce((sum, d) => sum + d.quantity, 0)
    },
    topProducts: Object.entries(topProducts)
      .sort((a, b) => b[1] - a[1])
      .slice(0, 5)
      .map(([product, sales]) => ({ product, sales })),
    topRegions: Object.entries(topRegions)
      .sort((a, b) => b[1] - a[1])
      .map(([region, sales]) => ({ region, sales })),
    categoryBreakdown: Object.entries(categoryStats)
      .map(([category, stats]) => ({ category, ...stats }))
  };
}

// 5. Interactive Data Exploration
dataExplorer = {
  const Inputs = require("@observablehq/inputs");

  // Create filters
  const categoryFilter = Inputs.select(['All', ...new Set(processedData.map(d => d.category))], {
    label: "Product Category",
    value: "All"
  });

  const regionFilter = Inputs.select(['All', ...new Set(processedData.map(d => d.region))], {
    label: "Sales Region",
    value: "All"
  });

  const priceRange = Inputs.range([0, 2000], {
    label: "Maximum Price",
    value: 2000,
    step: 50
  });

  return { categoryFilter, regionFilter, priceRange };
}

// 6. Filtered Data
filteredData = {
  const { categoryFilter, regionFilter, priceRange } = dataExplorer;

  return processedData.filter(d =>
    (categoryFilter === 'All' || d.category === categoryFilter) &&
    (regionFilter === 'All' || d.region === regionFilter) &&
    d.unit_price <= priceRange
  );
}

// 7. Visualization Dashboard
dashboard = {
  const plot = require("@observablehq/plot");

  // Sales trend chart
  const salesTrend = plot.plot({
    title: "Sales Trend",
    width: 800,
    height: 300,
    x: { label: "Date" },
    y: {
      label: "Sales ($)",
      tickFormat: (d) => `$${d.toLocaleString()}`
    },
    marks: [
      plot.line(filteredData, {
        x: "date",
        y: "total_sales",
        stroke: "#4e79a7"
      }),
      plot.dot(filteredData, {
        x: "date",
        y: "total_sales",
        fill: "#4e79a7",
        r: 3
      })
    ]
  });

  // Product distribution
  const productDistribution = plot.plot({
    title: "Product Sales Distribution",
    width: 400,
    height: 300,
    marks: [
      plot.barY(
        Object.entries(
          filteredData.reduce((acc, d) => {
            acc[d.product] = (acc[d.product] || 0) + d.total_sales;
            return acc;
          }, {})
        ).map(([product, sales]) => ({ product, sales }))
          .sort((a, b) => b.sales - a.sales)
          .slice(0, 10),
        {
          x: "sales",
          y: "product",
          fill: "#59a14f"
        }
      )
    ]
  });

  // Regional comparison
  const regionComparison = plot.plot({
    title: "Regional Sales Comparison",
    width: 400,
    height: 300,
    marks: [
      plot.barX(
        Object.entries(
          filteredData.reduce((acc, d) => {
            acc[d.region] = (acc[d.region] || 0) + d.total_sales;
            return acc;
          }, {})
        ).map(([region, sales]) => ({ region, sales })),
        {
          x: "sales",
          y: "region",
          fill: "#e15759"
        }
      )
    ]
  });

  return html`
    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;">
      <div style="grid-column: 1 / -1;">${salesTrend}</div>
      <div>${productDistribution}</div>
      <div>${regionComparison}</div>
    </div>
  `;
}

// 8. Statistical Analysis
statisticalAnalysis = {
  // Calculate correlations
  const correlation = (x, y) => {
    const n = x.length;
    const sumX = x.reduce((a, b) => a + b, 0);
    const sumY = y.reduce((a, b) => a + b, 0);
    const sumXY = x.reduce((total, xi, i) => total + xi * y[i], 0);
    const sumX2 = x.reduce((total, xi) => total + xi * xi, 0);
    const sumY2 = y.reduce((total, yi) => total + yi * yi, 0);

    const r = (n * sumXY - sumX * sumY) /
              Math.sqrt((n * sumX2 - sumX * sumX) * (n * sumY2 - sumY * sumY));

    return r;
  };

  const quantities = processedData.map(d => d.quantity);
  const prices = processedData.map(d => d.unit_price);
  const sales = processedData.map(d => d.total_sales);

  return {
    correlations: {
      quantity_price: correlation(quantities, prices),
      quantity_sales: correlation(quantities, sales),
      price_sales: correlation(prices, sales)
    },
    insights: correlation(quantities, prices) < -0.3 ?
      "⚠️ Negative correlation found: higher quantities associated with lower prices, indicating effective bulk discount strategy" :
      "📊 Quantity and price relationship is not strongly correlated, pricing strategy may need review"
  };
}

// 9. Trend Prediction
trendPrediction = {
  // Simple linear trend prediction
  const salesByDate = processedData.map(d => ({
    date: new Date(d.date).getTime(),
    sales: d.total_sales
  })).sort((a, b) => a.date - b.date);

  // Calculate linear regression
  const n = salesByDate.length;
  const x = salesByDate.map(d => d.date);
  const y = salesByDate.map(d => d.sales);

  const sumX = x.reduce((a, b) => a + b, 0);
  const sumY = y.reduce((a, b) => a + b, 0);
  const sumXY = x.reduce((total, xi, i) => total + xi * y[i], 0);
  const sumX2 = x.reduce((total, xi) => total + xi * xi, 0);

  const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
  const intercept = (sumY - slope * sumX) / n;

  // Predict next 7 days
  const lastDate = x[x.length - 1];
  const predictions = [];
  for (let i = 1; i <= 7; i++) {
    const futureDate = lastDate + (i * 24 * 60 * 60 * 1000);
    const predictedSales = slope * futureDate + intercept;
    predictions.push({
      date: new Date(futureDate),
      predictedSales: Math.max(0, predictedSales),
      confidence: 0.7
    });
  }

  return {
    trend: slope > 0 ? "📈 Upward Trend" : "📉 Downward Trend",
    slope,
    predictions,
    recommendation: slope > 0 ?
      "Sales showing upward trend, recommend increasing inventory and marketing investment" :
      "Sales showing downward trend, recommend analyzing causes and adjusting strategy"
  };
}

// 10. Report Generation
analysisReport = {
  const { categoryFilter, regionFilter } = dataExplorer;
  const filteredSummary = {
    records: filteredData.length,
    totalSales: filteredData.reduce((sum, d) => sum + d.total_sales, 0),
    avgOrderValue: filteredData.reduce((sum, d) => sum + d.total_sales, 0) / filteredData.length
  };

  return html`
    <div style="padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 15px; margin: 20px 0; font-family: Arial, sans-serif;">
      <h2 style="margin-top: 0;">📊 Data Analysis Report</h2>

      <h3>🔍 Current Filter Settings</h3>
      <p>• Product Category: ${categoryFilter}</p>
      <p>• Sales Region: ${regionFilter}</p>

      <h3>📈 Analysis Results</h3>
      <p>• Matching Records: <strong>${filteredSummary.records}</strong></p>
      <p>• Total Sales: <strong>$${filteredSummary.totalSales.toLocaleString()}</strong></p>
      <p>• Average Order Value: <strong>$${filteredSummary.avgOrderValue.toFixed(2)}</strong></p>

      <h3>🎯 Key Findings</h3>
      <p>${descriptiveStats.topProducts[0] ? `• Best Selling Product: ${descriptiveStats.topProducts[0].product} ($${descriptiveStats.topProducts[0].sales.toLocaleString()})` : ''}</p>
      <p>${descriptiveStats.topRegions[0] ? `• Best Performing Region: ${descriptiveStats.topRegions[0].region} ($${descriptiveStats.topRegions[0].sales.toLocaleString()})` : ''}</p>

      <h3>🔮 Trend Prediction</h3>
      <p>${trendPrediction.trend}</p>
      <p>${trendPrediction.recommendation}</p>

      <h3>⚡ Action Recommendations</h3>
      <p>• ${statisticalAnalysis.insights}</p>
      <p>• Focus on top-performing regions and products</p>
      <p>• Monitor sales trends and adjust strategy accordingly</p>
    </div>
  `;
}

💻 Observable Advanced Visualization Techniques text

🔴 complex

Explore advanced Observable visualization features including animations, maps, network graphs, and custom components

// Observable Notebook - Advanced Visualization Techniques Example
// Explore complex interactive visualization techniques

// 1. Import Required Libraries
libraries = {
  const d3 = require("d3@7");
  const plot = require("@observablehq/plot");
  const Inputs = require("@observablehq/inputs");
  const htl = require("htl");

  return { d3, plot, Inputs, htl };
}

// 2. Generate Complex Test Data
complexData = {
  const { d3 } = libraries;

  // Generate time series data
  const timeSeries = d3.range(100).map(i => ({
    date: new Date(2024, 0, i + 1),
    value: Math.sin(i / 10) * 50 + Math.random() * 20 + 100,
    category: ['A', 'B', 'C'][Math.floor(i / 33)],
    metadata: {
      source: ['sensor1', 'sensor2', 'sensor3'][i % 3],
      quality: Math.random() > 0.2 ? 'good' : 'poor'
    }
  }));

  // Generate geographical data
  const geoData = [
    { name: "Beijing", lat: 39.9042, lng: 116.4074, value: Math.random() * 100, category: "capital" },
    { name: "Shanghai", lat: 31.2304, lng: 121.4737, value: Math.random() * 100, category: "city" },
    { name: "Guangzhou", lat: 23.1291, lng: 113.2644, value: Math.random() * 100, category: "city" },
    { name: "Shenzhen", lat: 22.5431, lng: 114.0579, value: Math.random() * 100, category: "city" },
    { name: "Chengdu", lat: 30.5728, lng: 104.0668, value: Math.random() * 100, category: "city" },
    { name: "Hangzhou", lat: 30.2741, lng: 120.1551, value: Math.random() * 100, category: "city" },
    { name: "Wuhan", lat: 30.5928, lng: 114.3055, value: Math.random() * 100, category: "city" },
    { name: "Xi'an", lat: 34.2658, lng: 108.9541, value: Math.random() * 100, category: "city" },
    { name: "Chongqing", lat: 29.4316, lng: 106.9123, value: Math.random() * 100, category: "city" },
    { name: "Nanjing", lat: 32.0603, lng: 118.7969, value: Math.random() * 100, category: "city" }
  ];

  // Generate network graph data
  const networkNodes = d3.range(20).map(i => ({
    id: i,
    name: `Node ${i}`,
    group: Math.floor(i / 5),
    value: Math.random() * 50 + 10
  }));

  const networkLinks = [];
  for (let i = 0; i < 30; i++) {
    const source = Math.floor(Math.random() * networkNodes.length);
    let target = Math.floor(Math.random() * networkNodes.length);
    while (target === source) {
      target = Math.floor(Math.random() * networkNodes.length);
    }
    networkLinks.push({
      source,
      target,
      value: Math.random() * 10 + 1
    });
  }

  return { timeSeries, geoData, networkNodes, networkLinks };
}

// 3. Advanced Time Series Visualization
advancedTimeSeries = {
  const { plot, d3 } = libraries;
  const { timeSeries } = complexData;

  // Create multidimensional time series chart
  const chart = plot.plot({
    title: "Multidimensional Time Series Analysis",
    width: 900,
    height: 500,
    x: {
      type: "time",
      label: "Date",
      tickFormat: "%b %d"
    },
    y: {
      label: "Value",
      domain: [50, 150]
    },
    color: {
      domain: ["A", "B", "C"],
      range: ["#1f77b4", "#ff7f0e", "#2ca02c"]
    },
    facet: {
      data: timeSeries,
      y: "category"
    },
    marks: [
      // Main lines
      plot.line(timeSeries, {
        x: "date",
        y: "value",
        stroke: "category",
        strokeWidth: 2,
        curve: "cardinal"
      }),
      // Data points
      plot.dot(timeSeries, {
        x: "date",
        y: "value",
        fill: "category",
        r: 3,
        title: d => `${d.date.toLocaleDateString()}: ${d.value.toFixed(2)}`
      }),
      // Moving average line
      plot.line(
        timeSeries.map((d, i, arr) => ({
          date: d.date,
          value: arr.slice(Math.max(0, i - 5), i + 6).reduce((sum, x) => sum + x.value, 0) / Math.min(i + 1, 11)
        })),
        {
          x: "date",
          y: "value",
          stroke: "red",
          strokeDasharray: "5,5",
          strokeWidth: 2,
          opacity: 0.7
        }
      ),
      // Confidence interval
      plot.areaY(
        timeSeries.map(d => ({
          date: d.date,
          lower: d.value - 10,
          upper: d.value + 10
        })),
        {
          x: "date",
          y1: "lower",
          y2: "upper",
          fill: "#888",
          fillOpacity: 0.1
        }
      )
    ]
  });

  return chart;
}

// 4. Animation Particle System
particleAnimation = {
  const { htl, d3 } = libraries;

  // Particle system class
  class ParticleSystem {
    constructor(container) {
      this.container = container;
      this.width = 800;
      this.height = 400;
      this.particles = [];
      this.animationId = null;

      this.svg = d3.create("svg")
        .attr("width", this.width)
        .attr("height", this.height)
        .attr("viewBox", `0 0 ${this.width} ${this.height}`);

      this.initParticles();
      this.animate();
    }

    initParticles() {
      for (let i = 0; i < 100; i++) {
        this.particles.push({
          x: Math.random() * this.width,
          y: Math.random() * this.height,
          vx: (Math.random() - 0.5) * 2,
          vy: (Math.random() - 0.5) * 2,
          radius: Math.random() * 3 + 1,
          color: d3.schemeCategory10[Math.floor(Math.random() * 10)],
          life: 1
        });
      }

      this.particleElements = this.svg.selectAll("circle")
        .data(this.particles)
        .enter()
        .append("circle")
        .attr("r", d => d.radius)
        .attr("fill", d => d.color)
        .attr("opacity", d => d.life);
    }

    updateParticles() {
      this.particles.forEach(p => {
        p.x += p.vx;
        p.y += p.vy;
        p.life *= 0.995;

        // Boundary bounce
        if (p.x <= 0 || p.x >= this.width) p.vx *= -1;
        if (p.y <= 0 || p.y >= this.height) p.vy *= -1;

        // Respawn particle
        if (p.life < 0.1) {
          p.x = Math.random() * this.width;
          p.y = Math.random() * this.height;
          p.life = 1;
        }
      });

      this.particleElements
        .attr("cx", d => d.x)
        .attr("cy", d => d.y)
        .attr("opacity", d => d.life);
    }

    animate() {
      this.updateParticles();
      this.animationId = requestAnimationFrame(() => this.animate());
    }

    stop() {
      if (this.animationId) {
        cancelAnimationFrame(this.animationId);
      }
    }

    getNode() {
      return this.svg.node();
    }
  }

  // Create and start particle system
  const container = htl.svg`<svg width="800" height="400"></svg>`;
  const particleSystem = new ParticleSystem(container);

  // Auto-stop after 10 seconds (Observable auto-cleanup)
  setTimeout(() => particleSystem.stop(), 10000);

  return container;
}

// 5. Interactive Network Graph
interactiveNetwork = {
  const { d3 } = libraries;
  const { networkNodes, networkLinks } = complexData;

  const width = 800;
  const height = 600;

  // Create force-directed graph
  const simulation = d3.forceSimulation(networkNodes)
    .force("link", d3.forceLink(networkLinks).id(d => d.id).distance(100))
    .force("charge", d3.forceManyBody().strength(-300))
    .force("center", d3.forceCenter(width / 2, height / 2))
    .force("collision", d3.forceCollide().radius(d => d.value));

  const svg = d3.create("svg")
    .attr("width", width)
    .attr("height", height)
    .style("border", "1px solid #ccc");

  // Create arrow marker
  svg.append("defs").append("marker")
    .attr("id", "arrowhead")
    .attr("viewBox", "0 -5 10 10")
    .attr("refX", 15)
    .attr("refY", 0)
    .attr("markerWidth", 6)
    .attr("markerHeight", 6)
    .attr("orient", "auto")
    .append("path")
    .attr("d", "M0,-5L10,0L0,5")
    .attr("fill", "#999");

  // Create links
  const link = svg.append("g")
    .selectAll("line")
    .data(networkLinks)
    .enter()
    .append("line")
    .attr("stroke", "#999")
    .attr("stroke-opacity", 0.6)
    .attr("stroke-width", d => Math.sqrt(d.value))
    .attr("marker-end", "url(#arrowhead)");

  // Create nodes
  const node = svg.append("g")
    .selectAll("circle")
    .data(networkNodes)
    .enter()
    .append("circle")
    .attr("r", d => Math.sqrt(d.value))
    .attr("fill", d => d3.schemeCategory10[d.group])
    .attr("stroke", "#fff")
    .attr("stroke-width", 1.5)
    .call(d3.drag()
      .on("start", dragstarted)
      .on("drag", dragged)
      .on("end", dragended));

  // Add labels
  const label = svg.append("g")
    .selectAll("text")
    .data(networkNodes)
    .enter()
    .append("text")
    .text(d => d.name)
    .attr("font-size", "12px")
    .attr("dx", 12)
    .attr("dy", 4);

  // Add hover effects
  node.on("mouseover", function(event, d) {
    d3.select(this)
      .transition()
      .duration(200)
      .attr("r", d => Math.sqrt(d.value) * 1.5);
  })
  .on("mouseout", function(event, d) {
    d3.select(this)
      .transition()
      .duration(200)
      .attr("r", d => Math.sqrt(d.value));
  });

  // Update positions
  simulation.on("tick", () => {
    link
      .attr("x1", d => networkNodes[d.source].x)
      .attr("y1", d => networkNodes[d.source].y)
      .attr("x2", d => networkNodes[d.target].x)
      .attr("y2", d => networkNodes[d.target].y);

    node
      .attr("cx", d => d.x)
      .attr("cy", d => d.y);

    label
      .attr("x", d => d.x)
      .attr("y", d => d.y);
  });

  function dragstarted(event, d) {
    if (!event.active) simulation.alphaTarget(0.3).restart();
    d.fx = d.x;
    d.fy = d.y;
  }

  function dragged(event, d) {
    d.fx = event.x;
    d.fy = event.y;
  }

  function dragended(event, d) {
    if (!event.active) simulation.alphaTarget(0);
    d.fx = null;
    d.fy = null;
  }

  return svg.node();
}

// 6. Map Visualization
geoVisualization = {
  const { plot, d3 } = libraries;
  const { geoData } = complexData;

  // Create map projection
  const projection = d3.geoMercator()
    .center([104, 35])
    .scale(600)
    .translate([400, 300]);

  // Create base map (simplified version)
  const mapChart = plot.plot({
    title: "Geographic Data Visualization",
    width: 800,
    height: 600,
    projection: projection,
    marks: [
      // City points
      plot.dot(geoData, {
        x: "lng",
        y: "lat",
        r: d => d.value / 10,
        fill: d => d.category === "capital" ? "#e74c3c" : "#3498db",
        stroke: "#fff",
        strokeWidth: 2,
        title: d => `${d.name}: ${d.value.toFixed(1)}`
      }),
      // City labels
      plot.text(geoData, {
        x: "lng",
        y: "lat",
        text: "name",
        dy: -15,
        fontSize: 12,
        fill: "#333",
        textAnchor: "middle"
      }),
      // Connection lines (example)
      plot.link(geoData.slice(0, 5), {
        source: d => ({ lng: 116.4074, lat: 39.9042 }), // Beijing
        target: d => ({ lng: d.lng, lat: d.lat }),
        stroke: "#95a5a6",
        strokeOpacity: 0.5,
        strokeWidth: 1
      })
    ]
  });

  return mapChart;
}

// 7. Multidimensional Scatter Plot
multidimensionalScatter = {
  const { plot, d3 } = libraries;
  const { timeSeries } = complexData;

  // Prepare multidimensional data
  const multiDimData = timeSeries.map(d => ({
    x: d.value,
    y: d.value * (1 + Math.random() * 0.2),
    size: d.value / 10,
    color: d.category,
    label: d.date.toLocaleDateString(),
    quality: d.metadata.quality
  }));

  const chart = plot.plot({
    title: "Multidimensional Scatter Plot",
    width: 800,
    height: 600,
    x: { label: "Primary Variable" },
    y: { label: "Secondary Variable" },
    color: {
      domain: ["A", "B", "C"],
      range: ["#e74c3c", "#3498db", "#2ecc71"]
    },
    r: {
      range: [2, 10]
    },
    marks: [
      plot.dot(multiDimData, {
        x: "x",
        y: "y",
        fill: "color",
        r: "size",
        stroke: d => d.quality === "good" ? "#2c3e50" : "#e67e22",
        strokeWidth: 1,
        title: d => `${d.label}\nValue: ${d.x.toFixed(2)}\nQuality: ${d.quality}`,
        opacity: 0.8
      }),
      // Add trend line
      plot.line(
        d3.range(50, 150, 1).map(x => ({ x, y: x * 1.05 })),
        {
          x: "x",
          y: "y",
          stroke: "#34495e",
          strokeDasharray: "5,5",
          strokeWidth: 2
        }
      ),
      // Add density contours
      plot.contour(multiDimData, {
        x: "x",
        y: "y",
        stroke: "#3498db",
        strokeOpacity: 0.3,
        strokeWidth: 1,
        fill: "#3498db",
        fillOpacity: 0.1
      })
    ]
  });

  return chart;
}

// 8. Comprehensive Dashboard
comprehensiveDashboard = {
  const { plot, htl } = libraries;
  const { timeSeries, geoData } = complexData;

  // Create dashboard layout
  const dashboard = htl.html`
    <div style="display: grid; grid-template-columns: 2fr 1fr; gap: 20px; padding: 20px; background: #f8f9fa; border-radius: 10px;">
      <!-- Main chart area -->
      <div style="background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
        <h3 style="margin-top: 0;">📊 Time Trend Analysis</h3>
        ${plot.plot({
          width: 500,
          height: 300,
          x: { type: "time" },
          y: { grid: true },
          marks: [
            plot.line(timeSeries, {
              x: "date",
              y: "value",
              stroke: "category",
              strokeWidth: 2
            }),
            plot.dot(timeSeries, {
              x: "date",
              y: "value",
              fill: "category",
              r: 3
            })
          ]
        })}
      </div>

      <!-- Statistics panel -->
      <div style="background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
        <h3 style="margin-top: 0;">📈 Key Metrics</h3>
        ${(() => {
          const stats = {
            total: timeSeries.reduce((sum, d) => sum + d.value, 0),
            avg: timeSeries.reduce((sum, d) => sum + d.value, 0) / timeSeries.length,
            max: Math.max(...timeSeries.map(d => d.value)),
            min: Math.min(...timeSeries.map(d => d.value))
          };

          return htl.html`
            <div style="display: grid; gap: 10px;">
              <div style="padding: 10px; background: #e3f2fd; border-radius: 5px;">
                <strong>Total:</strong> ${stats.total.toFixed(2)}
              </div>
              <div style="padding: 10px; background: #f3e5f5; border-radius: 5px;">
                <strong>Average:</strong> ${stats.avg.toFixed(2)}
              </div>
              <div style="padding: 10px; background: #e8f5e8; border-radius: 5px;">
                <strong>Maximum:</strong> ${stats.max.toFixed(2)}
              </div>
              <div style="padding: 10px; background: #fff3e0; border-radius: 5px;">
                <strong>Minimum:</strong> ${stats.min.toFixed(2)}
              </div>
            </div>
          `;
        })()}
      </div>

      <!-- Map area -->
      <div style="background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
        <h3 style="margin-top: 0;">🗺️ Geographic Distribution</h3>
        ${plot.plot({
          width: 400,
          height: 300,
          projection: "natural-earth1",
          marks: [
            plot.dot(geoData, {
              x: "lng",
              y: "lat",
              r: d => d.value / 15,
              fill: d => d.category === "capital" ? "#e74c3c" : "#3498db",
              stroke: "#fff",
              strokeWidth: 2
            }),
            plot.text(geoData, {
              x: "lng",
              y: "lat",
              text: "name",
              dy: -10,
              fontSize: 10
            })
          ]
        })}
      </div>

      <!-- Category distribution -->
      <div style="background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
        <h3 style="margin-top: 0;">🎯 Category Analysis</h3>
        ${plot.plot({
          width: 400,
          height: 300,
          marks: [
            plot.arc(
              Object.entries(
                timeSeries.reduce((acc, d) => {
                  acc[d.category] = (acc[d.category] || 0) + 1;
                  return acc;
                }, {})
              ).map(([category, count]) => ({ category, count })),
              {
                fill: "category",
                r: "count",
                title: d => `${d.category}: ${d.count}`
              }
            )
          ]
        })}
      </div>
    </div>
  `;

  return dashboard;
}

// 9. Technical Summary Documentation
technicalSummary = {
  const { htl } = libraries;

  return htl.html`
    <div style="max-width: 900px; margin: 0 auto; padding: 20px; line-height: 1.6; font-family: Arial, sans-serif;">
      <h1>🎨 Observable Advanced Visualization Techniques Summary</h1>

      <h2>📚 Featured Technologies</h2>
      <ul>
        <li><strong>Time Series Analysis</strong>: Multi-dimensional data display, moving averages, confidence intervals</li>
        <li><strong>Particle Systems</strong>: Real-time animation, physics simulation, lifecycle management</li>
        <li><strong>Network Graphs</strong>: Force-directed layouts, drag interactions, dynamic updates</li>
        <li><strong>Geographic Visualization</strong>: Map projections, spatial data, geographic markers</li>
        <li><strong>Multidimensional Scatter Plots</strong>: Multi-variable mapping, density contours, trend analysis</li>
        <li><strong>Real-time Data Streams</strong>: Dynamic updates, streaming data processing</li>
        <li><strong>Comprehensive Dashboards</strong>: Responsive layouts, multi-chart integration</li>
      </ul>

      <h2>🛠️ Libraries Used</h2>
      <ul>
        <li><code>@observablehq/plot</code>: Declarative charting library</li>
        <li><code>@observablehq/inputs</code>: Interactive controls</li>
        <li><code>d3@7</code>: Data-driven documents library</li>
        <li><code>htl</code>: HTML template literals</li>
      </ul>

      <h2>💡 Best Practices</h2>
      <ul>
        <li>Use responsive design to ensure display across different devices</li>
        <li>Add appropriate interaction hints and animations to enhance user experience</li>
        <li>Use colors and visual encoding appropriately to improve data readability</li>
        <li>Pay attention to performance optimization, avoid over-rendering</li>
        <li>Use Observable's auto-cleanup mechanism for resource management</li>
      </ul>

      <h2>🚀 Advanced Recommendations</h2>
      <ul>
        <li>Try integrating WebGL for more complex 3D visualizations</li>
        <li>Explore machine learning algorithms in data discovery applications</li>
        <li>Combine with real-time APIs to create dynamically updating data sources</li>
        <li>Use Observable's collaboration features to share with team members</li>
      </ul>
    </div>
  `;
}