🎯 Exemples recommandés
Balanced sample collections from various categories for you to explore
Exemples Observable Notebooks
Exemples complets de Observable Notebooks incluant analyse de données, flux de travail de visualisation et techniques interactives avancées
💻 Création de Notebook de Base Observable javascript
🟢 simple
Apprenez à créer des Notebooks Observable en utilisant des cellules, des visualisations et des composants interactifs
// 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>
`;
}
💻 Flux de Travail d'Analyse de Données Observable javascript
🟡 intermediate
Construisez un flux de travail complet d'analyse de données incluant le nettoyage, la transformation, l'analyse et la visualisation
// 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>
`;
}
💻 Techniques de Visualisation Avancée Observable javascript
🔴 complex
Explorez les fonctionnalités avancées de visualisation Observable incluant les animations, cartes, graphiques réseau et composants personnalisés
// 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>
`;
}