🎯 Exemples recommandés
Balanced sample collections from various categories for you to explore
Exemples D3.js Data Visualization
Exemples complets de visualisation de données D3.js incluant graphiques, cartes, animations et visualisations interactives
💻 Graphiques de Base D3.js javascript
🟢 simple
⭐⭐
Graphiques D3.js essentiels incluant histogrammes, graphiques linéaires, camemberts et nuages de points interactifs
⏱️ 25 min
🏷️ d3, charts, visualization
Prerequisites:
JavaScript, HTML, CSS, D3.js basics
// D3.js Basic Charts Examples
// 1. Bar Chart
// HTML: <div id="bar-chart"></div>
function createBarChart() {
const data = [
{ name: 'Product A', value: 30 },
{ name: 'Product B', value: 80 },
{ name: 'Product C', value: 45 },
{ name: 'Product D', value: 60 },
{ name: 'Product E', value: 20 },
{ name: 'Product F', value: 90 },
{ name: 'Product G', value: 55 }
];
// Set dimensions
const margin = { top: 20, right: 30, bottom: 40, left: 40 };
const width = 600 - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;
// Create SVG
const svg = d3.select('#bar-chart')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
// Set scales
const x = d3.scaleBand()
.domain(data.map(d => d.name))
.range([0, width])
.padding(0.1);
const y = d3.scaleLinear()
.domain([0, d3.max(data, d => d.value)])
.nice()
.range([height, 0]);
// Create x-axis
svg.append('g')
.attr('transform', `translate(0,${height})`)
.call(d3.axisBottom(x))
.selectAll('text')
.style('text-anchor', 'end')
.attr('dx', '-.8em')
.attr('dy', '.15em')
.attr('transform', 'rotate(-45)');
// Create y-axis
svg.append('g')
.call(d3.axisLeft(y));
// Create bars
const bars = svg.selectAll('.bar')
.data(data)
.enter().append('rect')
.attr('class', 'bar')
.attr('x', d => x(d.name))
.attr('width', x.bandwidth())
.attr('y', d => y(d.value))
.attr('height', d => height - y(d.value))
.attr('fill', '#4e79a7')
.on('mouseover', function(event, d) {
d3.select(this)
.transition()
.duration(200)
.attr('fill', '#f28e2c');
// Show tooltip
const tooltip = d3.select('body').append('div')
.attr('class', 'tooltip')
.style('position', 'absolute')
.style('background', 'rgba(0,0,0,0.8)')
.style('color', 'white')
.style('padding', '8px')
.style('border-radius', '4px')
.style('pointer-events', 'none')
.style('opacity', 0);
tooltip.transition()
.duration(200)
.style('opacity', 1);
tooltip.html(`${d.name}: ${d.value}`)
.style('left', (event.pageX + 10) + 'px')
.style('top', (event.pageY - 10) + 'px');
})
.on('mouseout', function(d) {
d3.select(this)
.transition()
.duration(200)
.attr('fill', '#4e79a7');
// Remove tooltip
d3.selectAll('.tooltip').remove();
})
.on('mousemove', function(event) {
d3.select('.tooltip')
.style('left', (event.pageX + 10) + 'px')
.style('top', (event.pageY - 10) + 'px');
});
// Add labels
svg.append('text')
.attr('transform', 'rotate(-90)')
.attr('y', 0 - margin.left)
.attr('x', 0 - (height / 2))
.attr('dy', '1em')
.style('text-anchor', 'middle')
.text('Value');
svg.append('text')
.attr('transform', `translate(${width / 2}, ${height + margin.bottom})`)
.style('text-anchor', 'middle')
.text('Product');
}
// 2. Line Chart
// HTML: <div id="line-chart"></div>
function createLineChart() {
const data = [
{ date: new Date(2024, 0, 1), value: 30 },
{ date: new Date(2024, 1, 1), value: 45 },
{ date: new Date(2024, 2, 1), value: 35 },
{ date: new Date(2024, 3, 1), value: 50 },
{ date: new Date(2024, 4, 1), value: 65 },
{ date: new Date(2024, 5, 1), value: 55 },
{ date: new Date(2024, 6, 1), value: 70 },
{ date: new Date(2024, 7, 1), value: 85 },
{ date: new Date(2024, 8, 1), value: 75 },
{ date: new Date(2024, 9, 1), value: 90 },
{ date: new Date(2024, 10, 1), value: 80 },
{ date: new Date(2024, 11, 1), value: 95 }
];
// Set dimensions
const margin = { top: 20, right: 30, bottom: 40, left: 50 };
const width = 600 - margin.left - margin.right;
const height = 300 - margin.top - margin.bottom;
// Create SVG
const svg = d3.select('#line-chart')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
// Set scales
const x = d3.scaleTime()
.domain(d3.extent(data, d => d.date))
.range([0, width]);
const y = d3.scaleLinear()
.domain([0, d3.max(data, d => d.value)])
.nice()
.range([height, 0]);
// Create line generator
const line = d3.line()
.x(d => x(d.date))
.y(d => y(d.value))
.curve(d3.curveMonotoneX);
// Add grid lines
svg.append('g')
.attr('class', 'grid')
.attr('transform', `translate(0,${height})`)
.call(d3.axisBottom(x)
.tickSize(-height)
.tickFormat('')
)
.style('stroke-dasharray', '3,3')
.style('opacity', 0.3);
svg.append('g')
.attr('class', 'grid')
.call(d3.axisLeft(y)
.tickSize(-width)
.tickFormat('')
)
.style('stroke-dasharray', '3,3')
.style('opacity', 0.3);
// Create axes
svg.append('g')
.attr('transform', `translate(0,${height})`)
.call(d3.axisBottom(x));
svg.append('g')
.call(d3.axisLeft(y));
// Create line
svg.append('path')
.datum(data)
.attr('fill', 'none')
.attr('stroke', '#4e79a7')
.attr('stroke-width', 2)
.attr('d', line);
// Create area
const area = d3.area()
.x(d => x(d.date))
.y0(height)
.y1(d => y(d.value))
.curve(d3.curveMonotoneX);
svg.append('path')
.datum(data)
.attr('fill', '#4e79a7')
.attr('opacity', 0.1)
.attr('d', area);
// Add dots
svg.selectAll('.dot')
.data(data)
.enter().append('circle')
.attr('class', 'dot')
.attr('cx', d => x(d.date))
.attr('cy', d => y(d.value))
.attr('r', 4)
.attr('fill', '#4e79a7')
.on('mouseover', function(event, d) {
d3.select(this)
.transition()
.duration(200)
.attr('r', 6);
showTooltip(event, `Date: ${d.date.toLocaleDateString()}<br>Value: ${d.value}`);
})
.on('mouseout', function() {
d3.select(this)
.transition()
.duration(200)
.attr('r', 4);
hideTooltip();
});
}
// 3. Pie Chart
// HTML: <div id="pie-chart"></div>
function createPieChart() {
const data = [
{ label: 'Category A', value: 30, color: '#4e79a7' },
{ label: 'Category B', value: 25, color: '#f28e2c' },
{ label: 'Category C', value: 20, color: '#e15759' },
{ label: 'Category D', value: 15, color: '#76b7b2' },
{ label: 'Category E', value: 10, color: '#59a14f' }
];
// Set dimensions
const width = 400;
const height = 400;
const radius = Math.min(width, height) / 2;
// Create SVG
const svg = d3.select('#pie-chart')
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', `translate(${width / 2},${height / 2})`);
// Create pie layout
const pie = d3.pie()
.value(d => d.value)
.sort(null);
// Create arc generator
const arc = d3.arc()
.innerRadius(0)
.outerRadius(radius);
const arcHover = d3.arc()
.innerRadius(0)
.outerRadius(radius * 1.1);
// Create pie slices
const arcs = svg.selectAll('.arc')
.data(pie(data))
.enter().append('g')
.attr('class', 'arc');
arcs.append('path')
.attr('d', arc)
.attr('fill', d => d.data.color)
.attr('stroke', 'white')
.attr('stroke-width', 2)
.style('cursor', 'pointer')
.on('mouseover', function(event, d) {
d3.select(this)
.transition()
.duration(200)
.attr('d', arcHover);
showTooltip(event, `${d.data.label}: ${d.data.value} (${d3.format('.1%')(d.value / d3.sum(data, d => d.value))})`);
})
.on('mouseout', function(event, d) {
d3.select(this)
.transition()
.duration(200)
.attr('d', arc);
hideTooltip();
});
// Add labels
arcs.append('text')
.attr('transform', d => `translate(${arc.centroid(d)})`)
.attr('text-anchor', 'middle')
.style('fill', 'white')
.style('font-weight', 'bold')
.style('pointer-events', 'none')
.text(d => d.data.value);
}
// 4. Scatter Plot
// HTML: <div id="scatter-plot"></div>
function createScatterPlot() {
const data = Array.from({ length: 50 }, () => ({
x: Math.random() * 100,
y: Math.random() * 100,
size: Math.random() * 30 + 5,
category: ['A', 'B', 'C', 'D'][Math.floor(Math.random() * 4)]
}));
// Set dimensions
const margin = { top: 20, right: 30, bottom: 40, left: 50 };
const width = 600 - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;
// Create SVG
const svg = d3.select('#scatter-plot')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
// Set scales
const x = d3.scaleLinear()
.domain([0, 100])
.range([0, width]);
const y = d3.scaleLinear()
.domain([0, 100])
.range([height, 0]);
const size = d3.scaleLinear()
.domain([5, 35])
.range([3, 15]);
// Create axes
svg.append('g')
.attr('transform', `translate(0,${height})`)
.call(d3.axisBottom(x));
svg.append('g')
.call(d3.axisLeft(y));
// Create color scale
const color = d3.scaleOrdinal()
.domain(['A', 'B', 'C', 'D'])
.range(['#4e79a7', '#f28e2c', '#e15759', '#76b7b2']);
// Create dots
svg.selectAll('.dot')
.data(data)
.enter().append('circle')
.attr('class', 'dot')
.attr('cx', d => x(d.x))
.attr('cy', d => y(d.y))
.attr('r', d => size(d.size))
.attr('fill', d => color(d.category))
.attr('opacity', 0.7)
.style('cursor', 'pointer')
.on('mouseover', function(event, d) {
d3.select(this)
.transition()
.duration(200)
.attr('opacity', 1)
.attr('r', d => size(d.size) * 1.2);
showTooltip(event, `X: ${d.x.toFixed(1)}<br>Y: ${d.y.toFixed(1)}<br>Size: ${d.size.toFixed(1)}<br>Category: ${d.category}`);
})
.on('mouseout', function(event, d) {
d3.select(this)
.transition()
.duration(200)
.attr('opacity', 0.7)
.attr('r', d => size(d.size));
hideTooltip();
});
// Add axis labels
svg.append('text')
.attr('transform', 'rotate(-90)')
.attr('y', 0 - margin.left)
.attr('x', 0 - (height / 2))
.attr('dy', '1em')
.style('text-anchor', 'middle')
.text('Y Value');
svg.append('text')
.attr('transform', `translate(${width / 2}, ${height + margin.bottom})`)
.style('text-anchor', 'middle')
.text('X Value');
}
// Utility functions
function showTooltip(event, content) {
const tooltip = d3.select('body').append('div')
.attr('class', 'tooltip')
.style('position', 'absolute')
.style('background', 'rgba(0,0,0,0.8)')
.style('color', 'white')
.style('padding', '8px')
.style('border-radius', '4px')
.style('pointer-events', 'none')
.style('font-size', '12px')
.style('opacity', 0);
tooltip.transition()
.duration(200)
.style('opacity', 1);
tooltip.html(content)
.style('left', (event.pageX + 10) + 'px')
.style('top', (event.pageY - 10) + 'px');
}
function hideTooltip() {
d3.selectAll('.tooltip').remove();
}
// Initialize all charts
document.addEventListener('DOMContentLoaded', function() {
createBarChart();
createLineChart();
createPieChart();
createScatterPlot();
});
💻 Tableau de Bord Interactif D3.js javascript
🟡 intermediate
⭐⭐⭐⭐
Tableau de bord dynamique avec graphiques liés multiples, mises à jour en temps réel et filtres interactifs
⏱️ 45 min
🏷️ d3, dashboard, interactive, real-time
Prerequisites:
Advanced JavaScript, D3.js, HTML/CSS, Data visualization concepts
// D3.js Interactive Dashboard
// Dashboard Configuration
const dashboardConfig = {
width: 1200,
height: 800,
margin: { top: 20, right: 20, bottom: 40, left: 50 },
colors: ['#4e79a7', '#f28e2c', '#e15759', '#76b7b2', '#59a14f', '#edc949', '#af7aa1', '#ff9da7']
};
// Sample data generator
class DataGenerator {
constructor() {
this.categories = ['Electronics', 'Clothing', 'Books', 'Home', 'Sports'];
this.regions = ['North', 'South', 'East', 'West'];
}
generateSalesData(months = 12) {
const data = [];
const now = new Date();
for (let i = 0; i < months; i++) {
const date = new Date(now.getFullYear(), now.getMonth() - (months - 1 - i), 1);
this.categories.forEach(category => {
data.push({
date: date,
category: category,
sales: Math.floor(Math.random() * 10000) + 1000,
profit: Math.floor(Math.random() * 2000) + 200,
region: this.regions[Math.floor(Math.random() * this.regions.length)]
});
});
}
return data;
}
generateRealTimeData() {
return {
timestamp: new Date(),
value: Math.floor(Math.random() * 100) + 20,
category: this.categories[Math.floor(Math.random() * this.categories.length)],
region: this.regions[Math.floor(Math.random() * this.regions.length)]
};
}
}
// Main Dashboard Class
class InteractiveDashboard {
constructor(containerId) {
this.container = d3.select(containerId);
this.dataGenerator = new DataGenerator();
this.data = this.dataGenerator.generateSalesData();
this.filteredData = this.data;
this.filters = {
category: 'all',
region: 'all',
dateRange: [d3.min(this.data, d => d.date), d3.max(this.data, d => d.date)]
};
this.init();
}
init() {
this.createLayout();
this.createFilters();
this.createKPIs();
this.createCharts();
this.setupEventListeners();
this.startRealTimeUpdates();
}
createLayout() {
// Create dashboard container
this.dashboard = this.container.append('div')
.attr('class', 'dashboard')
.style('font-family', 'Arial, sans-serif')
.style('padding', '20px');
// Create header
this.dashboard.append('h1')
.text('Interactive Sales Dashboard')
.style('text-align', 'center')
.style('margin-bottom', '20px');
// Create filter container
this.filterContainer = this.dashboard.append('div')
.attr('class', 'filters')
.style('margin-bottom', '20px');
// Create KPI container
this.kpiContainer = this.dashboard.append('div')
.attr('class', 'kpis')
.style('display', 'flex')
.style('justify-content', 'space-between')
.style('margin-bottom', '20px');
// Create charts container
this.chartsContainer = this.dashboard.append('div')
.attr('class', 'charts')
.style('display', 'grid')
.style('grid-template-columns', '1fr 1fr')
.style('gap', '20px');
}
createFilters() {
// Category filter
this.filterContainer.append('label')
.text('Category: ')
.style('margin-right', '10px');
const categorySelect = this.filterContainer.append('select')
.attr('id', 'category-filter')
.style('margin-right', '20px');
categorySelect.selectAll('option')
.data(['all', ...this.dataGenerator.categories])
.enter()
.append('option')
.attr('value', d => d)
.text(d => d.charAt(0).toUpperCase() + d.slice(1));
// Region filter
this.filterContainer.append('label')
.text('Region: ')
.style('margin-right', '10px');
const regionSelect = this.filterContainer.append('select')
.attr('id', 'region-filter');
regionSelect.selectAll('option')
.data(['all', ...this.dataGenerator.regions])
.enter()
.append('option')
.attr('value', d => d)
.text(d);
// Date range filter
this.filterContainer.append('label')
.text('Date Range: ')
.style('margin-right', '10px');
const startDateInput = this.filterContainer.append('input')
.attr('type', 'date')
.attr('id', 'start-date')
.style('margin-right', '10px');
const endDateInput = this.filterContainer.append('input')
.attr('type', 'date')
.attr('id', 'end-date');
// Set default date range
const minDate = d3.min(this.data, d => d.date);
const maxDate = d3.max(this.data, d => d.date);
startDateInput.property('value', minDate.toISOString().split('T')[0]);
endDateInput.property('value', maxDate.toISOString().split('T')[0]);
}
createKPIs() {
const kpis = [
{ id: 'total-sales', label: 'Total Sales', value: 0, format: d3.format(',.0f') },
{ id: 'total-profit', label: 'Total Profit', value: 0, format: d3.format(',.0f') },
{ id: 'avg-sale', label: 'Average Sale', value: 0, format: d3.format(',.0f') },
{ id: 'top-category', label: 'Top Category', value: '-', format: d => d }
];
kpis.forEach(kpi => {
const kpiCard = this.kpiContainer.append('div')
.attr('class', 'kpi-card')
.style('background', 'white')
.style('padding', '20px')
.style('border-radius', '8px')
.style('box-shadow', '0 2px 4px rgba(0,0,0,0.1)')
.style('min-width', '200px');
kpiCard.append('h3')
.text(kpi.label)
.style('margin', '0 0 10px 0')
.style('color', '#666')
.style('font-size', '14px');
kpiCard.append('div')
.attr('id', kpi.id)
.style('font-size', '24px')
.style('font-weight', 'bold')
.style('color', '#333');
this[kpi.id] = kpiCard;
});
}
createCharts() {
this.createSalesTrendChart();
this.createCategoryDistribution();
this.createRegionalPerformance();
this.createRealTimeChart();
}
createSalesTrendChart() {
const chartContainer = this.chartsContainer.append('div')
.attr('class', 'chart')
.style('background', 'white')
.style('padding', '20px')
.style('border-radius', '8px')
.style('box-shadow', '0 2px 4px rgba(0,0,0,0.1)');
chartContainer.append('h3')
.text('Sales Trend')
.style('margin', '0 0 20px 0');
const margin = { top: 20, right: 30, bottom: 40, left: 50 };
const width = 500 - margin.left - margin.right;
const height = 300 - margin.top - margin.bottom;
const svg = chartContainer.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
// Set scales
const x = d3.scaleTime()
.domain(d3.extent(this.filteredData, d => d.date))
.range([0, width]);
const y = d3.scaleLinear()
.domain([0, d3.max(this.filteredData, d => d.sales)])
.nice()
.range([height, 0]);
// Create line generator
const line = d3.line()
.x(d => x(d.date))
.y(d => y(d.sales))
.curve(d3.curveMonotoneX);
// Create axes
svg.append('g')
.attr('transform', `translate(0,${height})`)
.call(d3.axisBottom(x));
svg.append('g')
.call(d3.axisLeft(y));
// Create line
svg.append('path')
.datum(this.filteredData)
.attr('fill', 'none')
.attr('stroke', '#4e79a7')
.attr('stroke-width', 2)
.attr('d', line);
this.salesTrendChart = { svg, x, y, line };
}
createCategoryDistribution() {
const chartContainer = this.chartsContainer.append('div')
.attr('class', 'chart')
.style('background', 'white')
.style('padding', '20px')
.style('border-radius', '8px')
.style('box-shadow', '0 2px 4px rgba(0,0,0,0.1)');
chartContainer.append('h3')
.text('Category Distribution')
.style('margin', '0 0 20px 0');
const width = 500;
const height = 300;
const radius = Math.min(width, height) / 2 - 20;
const svg = chartContainer.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', `translate(${width / 2},${height / 2})`);
// Aggregate data by category
const categoryData = d3.rollup(
this.filteredData,
v => d3.sum(v, d => d.sales),
d => d.category
);
const pieData = Array.from(categoryData, ([key, value]) => ({ key, value }));
// Create pie layout
const pie = d3.pie()
.value(d => d.value);
const arc = d3.arc()
.innerRadius(0)
.outerRadius(radius);
// Create pie slices
const arcs = svg.selectAll('.arc')
.data(pie(pieData))
.enter()
.append('g')
.attr('class', 'arc');
arcs.append('path')
.attr('d', arc)
.attr('fill', (d, i) => dashboardConfig.colors[i])
.attr('stroke', 'white')
.attr('stroke-width', 2);
this.categoryChart = { svg, pie, arc };
}
createRegionalPerformance() {
const chartContainer = this.chartsContainer.append('div')
.attr('class', 'chart')
.style('background', 'white')
.style('padding', '20px')
.style('border-radius', '8px')
.style('box-shadow', '0 2px 4px rgba(0,0,0,0.1)')
.style('grid-column', '1 / -1');
chartContainer.append('h3')
.text('Regional Performance')
.style('margin', '0 0 20px 0');
const margin = { top: 20, right: 30, bottom: 40, left: 50 };
const width = 1000 - margin.left - margin.right;
const height = 250 - margin.top - margin.bottom;
const svg = chartContainer.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
// Aggregate data by region
const regionData = d3.rollup(
this.filteredData,
v => d3.sum(v, d => d.sales),
d => d.region
);
const barData = Array.from(regionData, ([key, value]) => ({ key, value }));
// Set scales
const x = d3.scaleBand()
.domain(barData.map(d => d.key))
.range([0, width])
.padding(0.1);
const y = d3.scaleLinear()
.domain([0, d3.max(barData, d => d.value)])
.nice()
.range([height, 0]);
// Create bars
svg.selectAll('.bar')
.data(barData)
.enter()
.append('rect')
.attr('class', 'bar')
.attr('x', d => x(d.key))
.attr('width', x.bandwidth())
.attr('y', d => y(d.value))
.attr('height', d => height - y(d.value))
.attr('fill', '#4e79a7');
// Create axes
svg.append('g')
.attr('transform', `translate(0,${height})`)
.call(d3.axisBottom(x));
svg.append('g')
.call(d3.axisLeft(y));
this.regionalChart = { svg, x, y };
}
createRealTimeChart() {
const chartContainer = this.chartsContainer.append('div')
.attr('class', 'chart')
.style('background', 'white')
.style('padding', '20px')
.style('border-radius', '8px')
.style('box-shadow', '0 2px 4px rgba(0,0,0,0.1)');
chartContainer.append('h3')
.text('Real-time Metrics')
.style('margin', '0 0 20px 0');
const margin = { top: 20, right: 30, bottom: 40, left: 50 };
const width = 500 - margin.left - margin.right;
const height = 200 - margin.top - margin.bottom;
const svg = chartContainer.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
// Real-time data
this.realTimeData = [];
// Set scales
const x = d3.scaleTime()
.domain([new Date(Date.now() - 60000), new Date()])
.range([0, width]);
const y = d3.scaleLinear()
.domain([0, 100])
.range([height, 0]);
// Create axes
svg.append('g')
.attr('transform', `translate(0,${height})`)
.call(d3.axisBottom(x));
svg.append('g')
.call(d3.axisLeft(y));
// Create line
const line = d3.line()
.x(d => x(d.timestamp))
.y(d => d.value)
.curve(d3.curveMonotoneX);
const path = svg.append('path')
.datum(this.realTimeData)
.attr('fill', 'none')
.attr('stroke', '#f28e2c')
.attr('stroke-width', 2)
.attr('d', line);
this.realTimeChart = { svg, x, y, line, path };
}
setupEventListeners() {
// Category filter
d3.select('#category-filter').on('change', (event) => {
this.filters.category = event.target.value;
this.updateDashboard();
});
// Region filter
d3.select('#region-filter').on('change', (event) => {
this.filters.region = event.target.value;
this.updateDashboard();
});
// Date range filter
d3.select('#start-date').on('change', (event) => {
this.filters.dateRange[0] = new Date(event.target.value);
this.updateDashboard();
});
d3.select('#end-date').on('change', (event) => {
this.filters.dateRange[1] = new Date(event.target.value);
this.updateDashboard();
});
}
updateDashboard() {
// Apply filters
this.filteredData = this.data.filter(d => {
const categoryMatch = this.filters.category === 'all' || d.category === this.filters.category;
const regionMatch = this.filters.region === 'all' || d.region === this.filters.region;
const dateMatch = d.date >= this.filters.dateRange[0] && d.date <= this.filters.dateRange[1];
return categoryMatch && regionMatch && dateMatch;
});
// Update KPIs
this.updateKPIs();
// Update charts
this.updateCharts();
}
updateKPIs() {
const totalSales = d3.sum(this.filteredData, d => d.sales);
const totalProfit = d3.sum(this.filteredData, d => d.profit);
const avgSale = d3.mean(this.filteredData, d => d.sales);
const topCategory = d3.rollup(
this.filteredData,
v => d3.sum(v, d => d.sales),
d => d.category
);
const topCategoryName = Array.from(topCategory.entries())
.sort((a, b) => b[1] - a[1])[0]?.[0] || '-';
// Update KPI displays
d3.select('#total-sales').text(d3.format(',')(totalSales));
d3.select('#total-profit').text(d3.format(',')(totalProfit));
d3.select('#avg-sale').text(d3.format(',')(Math.round(avgSale)));
d3.select('#top-category').text(topCategoryName);
}
updateCharts() {
// Update sales trend chart
if (this.salesTrendChart) {
const { svg, x, y, line } = this.salesTrendChart;
x.domain(d3.extent(this.filteredData, d => d.date));
y.domain([0, d3.max(this.filteredData, d => d.sales)]);
svg.select('.x-axis').remove();
svg.select('.y-axis').remove();
svg.append('g')
.attr('transform', `translate(0,${y.range()[0]})`)
.call(d3.axisBottom(x));
svg.append('g')
.call(d3.axisLeft(y));
svg.select('path')
.datum(this.filteredData)
.transition()
.duration(750)
.attr('d', line);
}
// Update category chart
if (this.categoryChart) {
const { svg, pie, arc } = this.categoryChart;
const categoryData = d3.rollup(
this.filteredData,
v => d3.sum(v, d => d.sales),
d => d.category
);
const pieData = Array.from(categoryData, ([key, value]) => ({ key, value }));
const arcs = svg.selectAll('.arc')
.data(pie(pieData));
arcs.select('path')
.transition()
.duration(750)
.attrTween('d', function(d) {
const interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
return arc(interpolate(t));
};
});
}
// Update regional chart
if (this.regionalChart) {
const { svg, x, y } = this.regionalChart;
const regionData = d3.rollup(
this.filteredData,
v => d3.sum(v, d => d.sales),
d => d.region
);
const barData = Array.from(regionData, ([key, value]) => ({ key, value }));
x.domain(barData.map(d => d.key));
y.domain([0, d3.max(barData, d => d.value)]);
const bars = svg.selectAll('.bar')
.data(barData);
bars.transition()
.duration(750)
.attr('y', d => y(d.value))
.attr('height', d => y(0) - y(d.value));
}
}
startRealTimeUpdates() {
setInterval(() => {
const newData = this.dataGenerator.generateRealTimeData();
// Add to real-time data array
this.realTimeData.push(newData);
// Keep only last 60 seconds of data
const oneMinuteAgo = new Date(Date.now() - 60000);
this.realTimeData = this.realTimeData.filter(d => d.timestamp > oneMinuteAgo);
// Update real-time chart
if (this.realTimeChart) {
const { x, y, line, path } = this.realTimeChart;
x.domain([new Date(Date.now() - 60000), new Date()]);
path.datum(this.realTimeData)
.transition()
.duration(200)
.attr('d', line);
}
}, 1000);
}
}
// Initialize dashboard
document.addEventListener('DOMContentLoaded', function() {
const dashboard = new InteractiveDashboard('#dashboard');
});
💻 Cartes Géographiques D3.js javascript
🔴 complex
⭐⭐⭐⭐
Visualisations géographiques interactives incluant cartes mondiales, cartes choroplèthes et analyse spatiale
⏱️ 50 min
🏷️ d3, maps, geographic, spatial
Prerequisites:
Advanced D3.js, Geographic data concepts, TopoJSON, Projections
// D3.js Geographic Maps and Spatial Visualization
// 1. World Map with Data Points
function createWorldMap() {
const width = 960;
const height = 500;
// Create SVG
const svg = d3.select('#world-map')
.append('svg')
.attr('width', width)
.attr('height', height);
// Create projection
const projection = d3.geoNaturalEarth1()
.scale(width / (2 * Math.PI))
.translate([width / 2, height / 2]);
const path = d3.geoPath().projection(projection);
// Create zoom behavior
const zoom = d3.zoom()
.scaleExtent([1, 8])
.on('zoom', (event) => {
g.attr('transform', event.transform);
});
svg.call(zoom);
const g = svg.append('g');
// Sample data points
const cities = [
{ name: 'New York', lat: 40.7128, lon: -74.0060, population: 8419000, gdp: 1730000 },
{ name: 'London', lat: 51.5074, lon: -0.1278, population: 8982000, gdp: 615000 },
{ name: 'Tokyo', lat: 35.6762, lon: 139.6503, population: 13960000, gdp: 1520000 },
{ name: 'Paris', lat: 48.8566, lon: 2.3522, population: 2161000, gdp: 695000 },
{ name: 'Sydney', lat: -33.8688, lon: 151.2093, population: 5312000, gdp: 387000 },
{ name: 'São Paulo', lat: -23.5505, lon: -46.6333, population: 12330000, gdp: 430000 },
{ name: 'Mumbai', lat: 19.0760, lon: 72.8777, population: 20411000, gdp: 310000 },
{ name: 'Beijing', lat: 39.9042, lon: 116.4074, population: 21540000, gdp: 560000 }
];
// Load world map data
d3.json('https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json').then(function(world) {
// Draw countries
g.selectAll('.country')
.data(topojson.feature(world, world.objects.countries).features)
.enter().append('path')
.attr('class', 'country')
.attr('d', path)
.attr('fill', '#e0e0e0')
.attr('stroke', '#999')
.attr('stroke-width', 0.5)
.on('mouseover', function(event, d) {
d3.select(this)
.attr('fill', '#c0c0c0');
showTooltip(event, `${d.properties.name}`);
})
.on('mouseout', function() {
d3.select(this)
.attr('fill', '#e0e0e0');
hideTooltip();
});
// Draw cities
const cityCircles = g.selectAll('.city')
.data(cities)
.enter().append('circle')
.attr('class', 'city')
.attr('cx', d => projection([d.lon, d.lat])[0])
.attr('cy', d => projection([d.lon, d.lat])[1])
.attr('r', d => Math.sqrt(d.population / 1000000) * 3)
.attr('fill', '#4e79a7')
.attr('opacity', 0.7)
.attr('stroke', '#fff')
.attr('stroke-width', 1)
.on('mouseover', function(event, d) {
d3.select(this)
.transition()
.duration(200)
.attr('r', d => Math.sqrt(d.population / 1000000) * 4)
.attr('opacity', 1);
showTooltip(event, `
<strong>${d.name}</strong><br>
Population: ${d3.format(',')(d.population)}<br>
GDP: $${d3.format(',.0f')(d.gdp)}M
`);
})
.on('mouseout', function(event, d) {
d3.select(this)
.transition()
.duration(200)
.attr('r', d => Math.sqrt(d.population / 1000000) * 3)
.attr('opacity', 0.7);
hideTooltip();
});
// Add city labels
g.selectAll('.city-label')
.data(cities)
.enter().append('text')
.attr('class', 'city-label')
.attr('x', d => projection([d.lon, d.lat])[0])
.attr('y', d => projection([d.lon, d.lat])[1] - 10)
.attr('text-anchor', 'middle')
.attr('font-size', '10px')
.attr('font-weight', 'bold')
.attr('fill', '#333')
.text(d => d.name);
});
}
// 2. Choropleth Map
function createChoroplethMap() {
const width = 800;
const height = 500;
// Create SVG
const svg = d3.select('#choropleth-map')
.append('svg')
.attr('width', width)
.attr('height', height);
// Create projection for US states
const projection = d3.geoAlbersUsa()
.scale(1000)
.translate([width / 2, height / 2]);
const path = d3.geoPath().projection(projection);
// Sample data for US states
const stateData = {
'California': 39500000,
'Texas': 29100000,
'Florida': 21500000,
'New York': 20200000,
'Pennsylvania': 12900000,
'Illinois': 12600000,
'Ohio': 11700000,
'Georgia': 10700000,
'North Carolina': 10600000,
'Michigan': 10000000
};
// Create color scale
const colorScale = d3.scaleThreshold()
.domain([10000000, 20000000, 30000000, 40000000])
.range(['#fee5d9', '#fcae91', '#fb6a4a', '#de2d26', '#a50f15']);
// Load US states data
d3.json('https://cdn.jsdelivr.net/npm/us-atlas@3/states-10m.json').then(function(us) {
// Draw states
svg.selectAll('.state')
.data(topojson.feature(us, us.objects.states).features)
.enter().append('path')
.attr('class', 'state')
.attr('d', path)
.attr('fill', d => {
const population = stateData[d.properties.name] || 0;
return colorScale(population);
})
.attr('stroke', '#fff')
.attr('stroke-width', 1)
.on('mouseover', function(event, d) {
d3.select(this)
.attr('stroke', '#333')
.attr('stroke-width', 2);
const population = stateData[d.properties.name] || 0;
showTooltip(event, `
<strong>${d.properties.name}</strong><br>
Population: ${d3.format(',')(population)}
`);
})
.on('mouseout', function() {
d3.select(this)
.attr('stroke', '#fff')
.attr('stroke-width', 1);
hideTooltip();
});
// Create legend
const legend = svg.append('g')
.attr('class', 'legend')
.attr('transform', 'translate(650, 300)');
legend.append('text')
.attr('x', 0)
.attr('y', -10)
.text('Population')
.style('font-weight', 'bold');
const legendItems = legend.selectAll('.legend-item')
.data(colorScale.range())
.enter().append('g')
.attr('class', 'legend-item')
.attr('transform', (d, i) => `translate(0, ${i * 20})`);
legendItems.append('rect')
.attr('width', 18)
.attr('height', 18)
.attr('fill', d => d);
legendItems.append('text')
.attr('x', 24)
.attr('y', 9)
.attr('dy', '0.35em')
.text((d, i) => {
const thresholds = [10000000, 20000000, 30000000, 40000000];
return i === 0 ? `< ${d3.format(',')(thresholds[0])}` :
i === thresholds.length ? `> ${d3.format(',')(thresholds[thresholds.length - 1])}` :
`${d3.format(',')(thresholds[i-1])} - ${d3.format(',')(thresholds[i])}`;
});
});
}
// 3. Flight Routes Map
function createFlightRoutesMap() {
const width = 1000;
const height = 600;
// Create SVG
const svg = d3.select('#flight-routes-map')
.append('svg')
.attr('width', width)
.attr('height', height));
// Create projection
const projection = d3.geoNaturalEarth1()
.scale(width / (2 * Math.PI))
.translate([width / 2, height / 2]);
const path = d3.geoPath().projection(projection);
// Sample airport data
const airports = [
{ iata: 'JFK', name: 'New York JFK', lat: 40.6413, lon: -73.7781 },
{ iata: 'LAX', name: 'Los Angeles LAX', lat: 33.9425, lon: -118.4081 },
{ iata: 'ORD', name: 'Chicago O'Hare', lat: 41.9742, lon: -87.9073 },
{ iata: 'DFW', name: 'Dallas DFW', lat: 32.8998, lon: -97.0403 },
{ iata: 'DEN', name: 'Denver DEN', lat: 39.8617, lon: -104.6731 },
{ iata: 'SFO', name: 'San Francisco SFO', lat: 37.6213, lon: -122.3790 },
{ iata: 'SEA', name: 'Seattle SEA', lat: 47.4502, lon: -122.3088 },
{ iata: 'LAS', name: 'Las Vegas LAS', lat: 36.0840, lon: -115.1537 },
{ iata: 'PHX', name: 'Phoenix PHX', lat: 33.4484, lon: -112.0740 },
{ iata: 'IAH', name: 'Houston IAH', lat: 29.9902, lon: -95.3368 }
];
// Sample flight routes
const routes = [
{ from: 'JFK', to: 'LAX', passengers: 45000 },
{ from: 'JFK', to: 'SFO', passengers: 28000 },
{ from: 'LAX', to: 'ORD', passengers: 32000 },
{ from: 'ORD', to: 'DFW', passengers: 35000 },
{ from: 'DFW', to: 'IAH', passengers: 41000 },
{ from: 'SFO', to: 'SEA', passengers: 22000 },
{ from: 'DEN', to: 'LAS', passengers: 18000 },
{ from: 'PHX', to: 'LAX', passengers: 25000 },
{ from: 'SEA', to: 'SFO', passengers: 26000 },
{ from: 'LAS', to: 'PHX', passengers: 20000 }
];
// Load world map
d3.json('https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json').then(function(world) {
// Draw countries
svg.append('g')
.selectAll('.country')
.data(topojson.feature(world, world.objects.countries).features)
.enter().append('path')
.attr('class', 'country')
.attr('d', path)
.attr('fill', '#f0f0f0')
.attr('stroke', '#ddd')
.attr('stroke-width', 0.5);
// Create route lines
const routeLines = svg.append('g')
.selectAll('.route')
.data(routes)
.enter().append('path')
.attr('class', 'route')
.attr('d', d => {
const from = airports.find(a => a.iata === d.from);
const to = airports.find(a => a.iata === d.to);
const fromPoint = projection([from.lon, from.lat]);
const toPoint = projection([to.lon, to.lat]);
// Create curved path
const midX = (fromPoint[0] + toPoint[0]) / 2;
const midY = (fromPoint[1] + toPoint[1]) / 2 - 100;
return `M ${fromPoint[0]},${fromPoint[1]} Q ${midX},${midY} ${toPoint[0]},${toPoint[1]}`;
})
.attr('fill', 'none')
.attr('stroke', '#4e79a7')
.attr('stroke-width', d => Math.sqrt(d.passengers / 10000))
.attr('opacity', 0.3)
.on('mouseover', function(event, d) {
d3.select(this)
.transition()
.duration(200)
.attr('opacity', 0.8)
.attr('stroke', '#f28e2c');
const from = airports.find(a => a.iata === d.from);
const to = airports.find(a => a.iata === d.to);
showTooltip(event, `
${from.name} → ${to.name}<br>
Passengers: ${d3.format(',')(d.passengers)}
`);
})
.on('mouseout', function() {
d3.select(this)
.transition()
.duration(200)
.attr('opacity', 0.3)
.attr('stroke', '#4e79a7');
hideTooltip();
});
// Draw airports
svg.selectAll('.airport')
.data(airports)
.enter().append('circle')
.attr('class', 'airport')
.attr('cx', d => projection([d.lon, d.lat])[0])
.attr('cy', d => projection([d.lon, d.lat])[1])
.attr('r', 4)
.attr('fill', '#e15759')
.attr('stroke', '#fff')
.attr('stroke-width', 2)
.on('mouseover', function(event, d) {
d3.select(this)
.transition()
.duration(200)
.attr('r', 6);
showTooltip(event, `
<strong>${d.iata}</strong><br>
${d.name}<br>
Lat: ${d.lat.toFixed(2)}<br>
Lon: ${d.lon.toFixed(2)}
`);
})
.on('mouseout', function() {
d3.select(this)
.transition()
.duration(200)
.attr('r', 4);
hideTooltip();
});
// Add airport labels
svg.selectAll('.airport-label')
.data(airports)
.enter().append('text')
.attr('class', 'airport-label')
.attr('x', d => projection([d.lon, d.lat])[0])
.attr('y', d => projection([d.lon, d.lat])[1] + 15)
.attr('text-anchor', 'middle')
.attr('font-size', '10px')
.attr('font-weight', 'bold')
.attr('fill', '#333')
.text(d => d.iata);
});
}
// 4. Interactive Heatmap
function createHeatmap() {
const data = [];
const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
const hours = Array.from({ length: 24 }, (_, i) => i);
// Generate sample heatmap data
for (let day = 0; day < days.length; day++) {
for (let hour = 0; hour < 24; hour++) {
data.push({
day: days[day],
hour: hour,
value: Math.random() * 100,
dayIndex: day,
hourIndex: hour
});
}
}
const margin = { top: 50, right: 30, bottom: 30, left: 50 };
const width = 800 - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;
const cellSize = Math.floor(width / 24);
// Create SVG
const svg = d3.select('#heatmap')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
// Create color scale
const colorScale = d3.scaleSequential(d3.interpolateYlOrRd)
.domain([0, 100]);
// Create cells
svg.selectAll('.cell')
.data(data)
.enter().append('rect')
.attr('class', 'cell')
.attr('x', d => d.hour * cellSize)
.attr('y', d => d.day * cellSize)
.attr('width', cellSize)
.attr('height', cellSize)
.attr('fill', d => colorScale(d.value))
.attr('stroke', '#fff')
.attr('stroke-width', 1)
.on('mouseover', function(event, d) {
d3.select(this)
.attr('stroke', '#333')
.attr('stroke-width', 2);
showTooltip(event, `
<strong>${days[d.dayIndex]} ${String(d.hour).padStart(2, '0')}:00</strong><br>
Activity: ${d.value.toFixed(1)}%
`);
})
.on('mouseout', function() {
d3.select(this)
.attr('stroke', '#fff')
.attr('stroke-width', 1);
hideTooltip();
});
// Add day labels
svg.selectAll('.day-label')
.data(days)
.enter().append('text')
.attr('class', 'day-label')
.attr('x', -10)
.attr('y', (d, i) => i * cellSize + cellSize / 2)
.attr('dy', '0.35em')
.attr('text-anchor', 'end')
.style('font-size', '12px')
.text(d => d);
// Add hour labels
svg.selectAll('.hour-label')
.data(hours)
.enter().append('text')
.attr('class', 'hour-label')
.attr('x', (d, i) => i * cellSize + cellSize / 2)
.attr('y', height + 20)
.attr('text-anchor', 'middle')
.style('font-size', '12px')
.text(d => d);
// Add title
svg.append('text')
.attr('x', width / 2)
.attr('y', -20)
.attr('text-anchor', 'middle')
.style('font-size', '16px')
.style('font-weight', 'bold')
.text('Weekly Activity Heatmap');
}
// Utility functions
function showTooltip(event, content) {
const tooltip = d3.select('body').append('div')
.attr('class', 'tooltip')
.style('position', 'absolute')
.style('background', 'rgba(0,0,0,0.8)')
.style('color', 'white')
.style('padding', '10px')
.style('border-radius', '4px')
.style('pointer-events', 'none')
.style('font-size', '12px')
.style('max-width', '200px')
.style('opacity', 0);
tooltip.transition()
.duration(200)
.style('opacity', 1);
tooltip.html(content)
.style('left', (event.pageX + 10) + 'px')
.style('top', (event.pageY - 10) + 'px');
}
function hideTooltip() {
d3.selectAll('.tooltip').remove();
}
// Initialize all maps
document.addEventListener('DOMContentLoaded', function() {
createWorldMap();
createChoroplethMap();
createFlightRoutesMap();
createHeatmap();
});