🎯 Рекомендуемые коллекции
Балансированные коллекции примеров кода из различных категорий, которые вы можете исследовать
Примеры Observable Notebooks
Комплексные примеры Observable Notebooks включая анализ данных, рабочие процессы визуализации и продвинутые интерактивные техники
💻 Создание базового ноутбука Observable javascript
🟢 simple
Изучите создание Observable Notebooks с использованием ячеек, визуализаций и интерактивных компонентов
// 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 javascript
🟡 intermediate
Создайте полный рабочий процесс анализа данных, включая очистку, преобразование, анализ и визуализацию
// 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 javascript
🔴 complex
Исследуйте расширенные функции визуализации Observable, включая анимации, карты, сетевые графы и пользовательские компоненты
// 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>
`;
}