Practice and reinforce the concepts from Lesson 7
In this activity, you'll:
Total time: 60-75 minutes
Access the template here: Chart Visualization Template
git clone
and navigate to the template folderindex.html
in your browser to start!What you'll do: Pick your favorite data visualization project Time: 5 minutes
Choose one of these exciting visualization projects:
Visualize Pokemon battle stats in beautiful radar charts
Create timeline charts of character appearances
Show weather trends and forecasts
Display crypto price movements and market data
What you'll do: Build your first data chart using Chart.js Time: 15 minutes
// Sample Pokemon data (you'll replace with real API data later)
const pokemonData = {
labels: ['Attack', 'Defense', 'Speed', 'HP', 'Sp. Attack', 'Sp. Defense'],
datasets: [{
label: 'Pikachu',
data: [55, 40, 90, 35, 50, 50],
backgroundColor: 'rgba(255, 206, 84, 0.2)',
borderColor: 'rgba(255, 206, 84, 1)',
borderWidth: 2
}]
};
// Create the radar chart
const ctx = document.getElementById('pokemonChart').getContext('2d');
const pokemonChart = new Chart(ctx, {
type: 'radar',
data: pokemonData,
options: {
responsive: true,
plugins: {
title: {
display: true,
text: 'Pokemon Battle Stats Comparison'
}
},
scales: {
r: {
beginAtZero: true,
max: 100
}
}
}
});
const characterData = {
labels: ['Luke Skywalker', 'Darth Vader', 'Princess Leia', 'Han Solo', 'Chewbacca'],
datasets: [{
label: 'Height (cm)',
data: [172, 202, 150, 180, 228],
backgroundColor: [
'rgba(54, 162, 235, 0.5)',
'rgba(255, 99, 132, 0.5)',
'rgba(255, 206, 84, 0.5)',
'rgba(75, 192, 192, 0.5)',
'rgba(153, 102, 255, 0.5)'
],
borderColor: [
'rgb(54, 162, 235)',
'rgb(255, 99, 132)',
'rgb(255, 206, 84)',
'rgb(75, 192, 192)',
'rgb(153, 102, 255)'
],
borderWidth: 1
}]
};
const ctx = document.getElementById('characterChart').getContext('2d');
const characterChart = new Chart(ctx, {
type: 'bar',
data: characterData,
options: {
responsive: true,
plugins: {
title: {
display: true,
text: 'Star Wars Character Heights'
}
},
scales: {
y: {
beginAtZero: true
}
}
}
});
const temperatureData = {
labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
datasets: [{
label: 'Temperature (°C)',
data: [22, 25, 23, 27, 24, 26, 28],
borderColor: 'rgb(255, 99, 132)',
backgroundColor: 'rgba(255, 99, 132, 0.2)',
tension: 0.1
}]
};
const ctx = document.getElementById('temperatureChart').getContext('2d');
const temperatureChart = new Chart(ctx, {
type: 'line',
data: temperatureData,
options: {
responsive: true,
plugins: {
title: {
display: true,
text: '7-Day Temperature Forecast'
}
}
}
});
Your task:
What you'll do: Replace sample data with real API data Time: 20 minutes
async function fetchPokemonStats(pokemonName) {
try {
const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${pokemonName.toLowerCase()}`);
if (!response.ok) throw new Error(`Pokemon ${pokemonName} not found`);
const pokemon = await response.json();
// Extract stats for radar chart
const stats = pokemon.stats.map(stat => stat.base_stat);
const statNames = pokemon.stats.map(stat => stat.stat.name);
return {
name: pokemon.name,
stats: stats,
statNames: statNames,
types: pokemon.types.map(t => t.type.name),
sprite: pokemon.sprites.front_default
};
} catch (error) {
console.error('Error fetching Pokemon data:', error);
throw error;
}
}
async function createPokemonRadarChart() {
try {
const pokemonData = await fetchPokemonStats('pikachu');
const chartData = {
labels: pokemonData.statNames.map(name =>
name.charAt(0).toUpperCase() + name.slice(1)
),
datasets: [{
label: pokemonData.name.charAt(0).toUpperCase() + pokemonData.name.slice(1),
data: pokemonData.stats,
backgroundColor: 'rgba(255, 206, 84, 0.2)',
borderColor: 'rgba(255, 206, 84, 1)',
borderWidth: 2
}]
};
const ctx = document.getElementById('pokemonChart').getContext('2d');
const chart = new Chart(ctx, {
type: 'radar',
data: chartData,
options: {
responsive: true,
plugins: {
title: {
display: true,
text: `${pokemonData.name} Battle Stats`
}
},
scales: {
r: {
beginAtZero: true,
max: 150
}
}
}
});
return chart;
} catch (error) {
console.error('Failed to create Pokemon chart:', error);
}
}
// Call the function when page loads
createPokemonRadarChart();
async function fetchCharacterData() {
try {
const characterIds = [1, 2, 3, 4, 13]; // Luke, C-3PO, R2-D2, Vader, Chewbacca
const characterPromises = characterIds.map(id =>
fetch(`https://swapi.dev/api/people/${id}/`)
.then(response => response.json())
);
const characters = await Promise.all(characterPromises);
return characters.map(char => ({
name: char.name,
height: parseInt(char.height) || 0,
mass: parseInt(char.mass) || 0,
films: char.films.length
}));
} catch (error) {
console.error('Error fetching character data:', error);
return [];
}
}
async function createCharacterChart() {
const characters = await fetchCharacterData();
const chartData = {
labels: characters.map(c => c.name),
datasets: [{
label: 'Height (cm)',
data: characters.map(c => c.height),
backgroundColor: 'rgba(54, 162, 235, 0.5)',
borderColor: 'rgb(54, 162, 235)',
borderWidth: 1
}]
};
const ctx = document.getElementById('characterChart').getContext('2d');
const chart = new Chart(ctx, {
type: 'bar',
data: chartData,
options: {
responsive: true,
plugins: {
title: {
display: true,
text: 'Star Wars Character Heights'
}
}
}
});
}
createCharacterChart();
async function fetchWeatherForecast(city = 'London') {
try {
const apiKey = 'your-api-key-here'; // Get your free key from OpenWeatherMap
const url = `https://api.openweathermap.org/data/2.5/forecast?q=${city}&appid=${apiKey}&units=metric`;
const response = await fetch(url);
if (!response.ok) throw new Error('Weather data not available');
const data = await response.json();
// Get next 7 data points (21 hours, 3-hour intervals)
const forecastData = data.list.slice(0, 7).map(item => ({
time: new Date(item.dt * 1000).toLocaleDateString('en-US', {
weekday: 'short',
hour: '2-digit'
}),
temperature: Math.round(item.main.temp),
humidity: item.main.humidity,
description: item.weather[0].description
}));
return forecastData;
} catch (error) {
console.error('Error fetching weather data:', error);
// Return sample data as fallback
return [
{ time: 'Mon 12', temperature: 22, humidity: 65 },
{ time: 'Mon 15', temperature: 25, humidity: 60 },
{ time: 'Mon 18', temperature: 23, humidity: 70 },
{ time: 'Tue 00', temperature: 20, humidity: 75 },
{ time: 'Tue 03', temperature: 18, humidity: 80 },
{ time: 'Tue 06', temperature: 19, humidity: 78 },
{ time: 'Tue 09', temperature: 24, humidity: 65 }
];
}
}
async function createWeatherChart() {
const forecastData = await fetchWeatherForecast();
const chartData = {
labels: forecastData.map(item => item.time),
datasets: [{
label: 'Temperature (°C)',
data: forecastData.map(item => item.temperature),
borderColor: 'rgb(255, 99, 132)',
backgroundColor: 'rgba(255, 99, 132, 0.2)',
tension: 0.1
}]
};
const ctx = document.getElementById('temperatureChart').getContext('2d');
const chart = new Chart(ctx, {
type: 'line',
data: chartData,
options: {
responsive: true,
plugins: {
title: {
display: true,
text: 'Weather Forecast'
}
}
}
});
}
createWeatherChart();
Your task:
What you'll do: Add a second chart to show different data insights Time: 15 minutes
async function createPokemonTypeChart() {
try {
// Fetch multiple Pokemon to analyze types
const pokemonNames = ['pikachu', 'charizard', 'blastoise', 'venusaur', 'alakazam'];
const pokemonData = await Promise.all(
pokemonNames.map(name => fetchPokemonStats(name))
);
// Count types
const typeCount = {};
pokemonData.forEach(pokemon => {
pokemon.types.forEach(type => {
typeCount[type] = (typeCount[type] || 0) + 1;
});
});
const chartData = {
labels: Object.keys(typeCount),
datasets: [{
data: Object.values(typeCount),
backgroundColor: [
'#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF',
'#FF9F40', '#FF6384', '#36A2EB'
]
}]
};
const ctx = document.getElementById('typeChart').getContext('2d');
new Chart(ctx, {
type: 'doughnut',
data: chartData,
options: {
responsive: true,
plugins: {
title: {
display: true,
text: 'Pokemon Type Distribution'
}
}
}
});
} catch (error) {
console.error('Failed to create type chart:', error);
}
}
createPokemonTypeChart();
async function createFilmsScatterChart() {
const characters = await fetchCharacterData();
// Create scatter data (height vs mass)
const scatterData = characters
.filter(c => c.height > 0 && c.mass > 0)
.map(c => ({
x: c.height,
y: c.mass,
label: c.name
}));
const chartData = {
datasets: [{
label: 'Characters',
data: scatterData,
backgroundColor: 'rgba(75, 192, 192, 0.6)',
borderColor: 'rgba(75, 192, 192, 1)',
}]
};
const ctx = document.getElementById('scatterChart').getContext('2d');
new Chart(ctx, {
type: 'scatter',
data: chartData,
options: {
responsive: true,
plugins: {
title: {
display: true,
text: 'Character Height vs Mass'
},
tooltip: {
callbacks: {
label: function(context) {
const point = context.raw;
return `${point.label}: ${point.x}cm, ${point.y}kg`;
}
}
}
},
scales: {
x: {
title: {
display: true,
text: 'Height (cm)'
}
},
y: {
title: {
display: true,
text: 'Mass (kg)'
}
}
}
}
});
}
createFilmsScatterChart();
async function createHumidityChart() {
const forecastData = await fetchWeatherForecast();
const chartData = {
labels: forecastData.map(item => item.time),
datasets: [{
label: 'Humidity (%)',
data: forecastData.map(item => item.humidity),
backgroundColor: 'rgba(54, 162, 235, 0.5)',
borderColor: 'rgb(54, 162, 235)',
borderWidth: 1
}]
};
const ctx = document.getElementById('humidityChart').getContext('2d');
new Chart(ctx, {
type: 'bar',
data: chartData,
options: {
responsive: true,
plugins: {
title: {
display: true,
text: 'Humidity Levels'
}
},
scales: {
y: {
beginAtZero: true,
max: 100
}
}
}
});
}
createHumidityChart();
Your task:
What you'll do: Make your charts interactive and dynamic Time: 15 minutes
async function comparePokemon() {
const pokemon1 = document.getElementById('pokemon1Input').value || 'pikachu';
const pokemon2 = document.getElementById('pokemon2Input').value || 'charizard';
try {
const [poke1Data, poke2Data] = await Promise.all([
fetchPokemonStats(pokemon1),
fetchPokemonStats(pokemon2)
]);
// Update chart with both Pokemon
const chartData = {
labels: poke1Data.statNames.map(name =>
name.charAt(0).toUpperCase() + name.slice(1)
),
datasets: [
{
label: poke1Data.name,
data: poke1Data.stats,
backgroundColor: 'rgba(255, 206, 84, 0.2)',
borderColor: 'rgba(255, 206, 84, 1)',
borderWidth: 2
},
{
label: poke2Data.name,
data: poke2Data.stats,
backgroundColor: 'rgba(54, 162, 235, 0.2)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 2
}
]
};
// Clear existing chart and create new one
Chart.getChart('pokemonChart')?.destroy();
const ctx = document.getElementById('pokemonChart').getContext('2d');
new Chart(ctx, {
type: 'radar',
data: chartData,
options: {
responsive: true,
plugins: {
title: {
display: true,
text: `${poke1Data.name} vs ${poke2Data.name}`
}
},
scales: {
r: {
beginAtZero: true,
max: 150
}
}
}
});
} catch (error) {
alert('Could not find one or both Pokemon. Try different names!');
}
}
// Add event listener for comparison button
document.getElementById('compareBtn').addEventListener('click', comparePokemon);
function updateChartsForCity(city) {
// Clear existing charts
Chart.getChart('temperatureChart')?.destroy();
Chart.getChart('humidityChart')?.destroy();
// Recreate charts with new city data
createWeatherChart(city);
createHumidityChart(city);
// Update display
document.getElementById('currentCity').textContent = city;
}
// Add event listener for city selection
document.getElementById('citySelect').addEventListener('change', (event) => {
updateChartsForCity(event.target.value);
});
function showLoading(chartId) {
const container = document.getElementById(chartId).parentElement;
container.innerHTML = `
<div class="loading-spinner">
<div class="spinner"></div>
<p>Loading chart data...</p>
</div>
`;
}
function hideLoading(chartId) {
const container = document.getElementById(chartId).parentElement;
container.innerHTML = `<canvas id="${chartId}"></canvas>`;
}
Your task:
Try these features:
Easy: Add chart animations and hover effects
Medium: Create a data export feature (download chart as image)
Hard: Build a real-time updating dashboard with fresh data
Excellent work on creating data visualizations! You've learned how to transform API data into beautiful, interactive charts. In the next lesson, we'll explore local storage and caching to improve performance and user experience.
Charts not showing?
API not working?
Performance issues?