Observable Notebooks Beispiele

Umfassende Observable Notebook-Beispiele einschließlich Datenanalyse, Visualisierungs-Workflows und fortgeschrittene interaktive Techniken

💻 Observable Basis Notebook Erstellung javascript

🟢 simple

Lernen Sie, Observable Notebooks mit Zellen, Visualisierungen und interaktiven Komponenten zu erstellen

// 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 Datenanalyse-Workflow javascript

🟡 intermediate

Bauen Sie einen vollständigen Datenanalyse-Workflow mit Datenbereinigung, Transformation, Analyse und Visualisierung

// 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 Fortgeschrittene Visualisierungstechniken javascript

🔴 complex

Erkunden Sie erweiterte Observable-Visualisierungsfunktionen einschließlich Animationen, Karten, Netzwerkgraphen und benutzerdefinierten Komponenten

// 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>
  `;
}