Skip to content

Commit

Permalink
Made the animations better
Browse files Browse the repository at this point in the history
  • Loading branch information
andresnowak committed Dec 17, 2024
1 parent 9b61a5c commit 4e67722
Show file tree
Hide file tree
Showing 3 changed files with 194 additions and 92 deletions.
264 changes: 180 additions & 84 deletions app/data_charts.js
Original file line number Diff line number Diff line change
@@ -1,119 +1,215 @@
import { useEffect, useState } from "react";
import { useEffect, useState, useRef } from "react";
import Chart from "chart.js/auto";
import Papa from "papaparse";

function TimeChartComponent() {
const [currentYear, setCurrentYear] = useState(2000);
const TimeChartComponent = () => {
const chartRef = useRef(null);
const chartInstance = useRef(null);
const [isPlaying, setIsPlaying] = useState(false);
const [data, setData] = useState(null);
const [currentDate, setCurrentDate] = useState(null);
const [allDates, setAllDates] = useState([]);
const colorMap = useRef({});

const colors = [
"#3e95cd",
"#8e5ea2",
"#3cba9f",
"#e8c3b9",
"#c45850",
"#ff9f40",
"#4bc0c0",
"#36a2eb",
"#ff6384",
"#9966ff",
];

useEffect(() => {
const loadData = async () => {
const response = await fetch("./data.csv");
const response = await fetch("./time_data.csv");
const reader = response.body.getReader();
const result = await reader.read();
const decoder = new TextDecoder("utf-8");
const csv = decoder.decode(result.value);

Papa.parse(csv, {
complete: (results) => setData(results.data.slice(1))
header: true,
complete: (results) => {
const dateColumns = Object.keys(results.data[0]).filter(
(key) => key !== "country"
);
setAllDates(dateColumns);
setCurrentDate(dateColumns[0]);
setData(results.data);

results.data.forEach((item, index) => {
colorMap.current[item.country] =
colors[index % colors.length];
});
},
});
};

loadData();

return () => {
if (animationFrameId.current) {
cancelAnimationFrame(animationFrameId.current);
}
};
}, []);

useEffect(() => {
let intervalId;
if (isPlaying && data) {
intervalId = setInterval(() => {
setCurrentYear(year => {
if (year >= 2023) {
setIsPlaying(false);
return 2000;
}
return year + 1;
});
}, 1000);
}
return () => clearInterval(intervalId);
}, [isPlaying, data]);
if (!data || !currentDate) return;

useEffect(() => {
if (!data) return;

const yearData = data.find(row => row[0] === currentYear.toString());
if (!yearData) return;

// Create sorted data with labels
const sortedData = yearData.slice(1)
.map((value, index) => ({
value: parseFloat(value),
label: `Column ${index + 1}`,
color: [
'rgba(75, 192, 192, 0.6)',
'rgba(255, 99, 132, 0.6)',
'rgba(54, 162, 235, 0.6)',
'rgba(255, 206, 86, 0.6)'
][index]
}))
.sort((a, b) => b.value - a.value);

const ctx = document.getElementById("timeChartCanvas");
const chart = new Chart(ctx, {
type: 'bar',
data: {
labels: sortedData.map(d => d.label),
datasets: [{
data: sortedData.map(d => d.value),
backgroundColor: sortedData.map(d => d.color)
}]
},
options: {
animation: {
duration: 800,
easing: 'easeInOutQuart'
const sortedData = [...data].sort(
(a, b) => b[currentDate] - a[currentDate]
);

if (!chartInstance.current) {
const ctx = chartRef.current.getContext("2d");

chartInstance.current = new Chart(ctx, {
type: "bar",
data: {
labels: sortedData.map((item) => item.country),
datasets: [
{
data: sortedData.map((item) => item[currentDate]),
backgroundColor: sortedData.map(
(item) => colorMap.current[item.country]
),
borderWidth: 2,
borderRadius: 4,
borderColor: sortedData.map(
(item) => colorMap.current[item.country]
),
},
],
},
scales: {
y: {
beginAtZero: true
}
options: {
indexAxis: "y",
animation: {
duration: 1000,
easing: "easeInOutQuart",
},
transitions: {
active: {
animation: {
duration: 1000,
},
},
},
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
title: {
display: true,
text: currentDate,
font: { size: 24, weight: "bold" },
padding: 20,
color: "#333",
},
tooltip: {
callbacks: {
label: (context) => {
return `${context.formattedValue} units`;
},
},
},
},
scales: {
x: {
beginAtZero: true,
grid: { display: false },
ticks: {
font: {
size: 14,
},
color: "#666",
},
},
y: {
grid: { display: false },
ticks: {
font: {
size: 14,
weight: "bold",
},
color: "#333",
},
},
},
layout: {
padding: {
right: 20,
},
},
},
plugins: {
legend: {
display: false
}
}
}
});
});
} else {
chartInstance.current.data.labels = sortedData.map(
(item) => item.country
);
chartInstance.current.data.datasets[0].data = sortedData.map(
(item) => item[currentDate]
);
chartInstance.current.data.datasets[0].backgroundColor =
sortedData.map((item) => colorMap.current[item.country]);
chartInstance.current.data.datasets[0].borderColor = sortedData.map(
(item) => colorMap.current[item.country]
);
chartInstance.current.options.plugins.title.text = currentDate;
chartInstance.current.update("default");
}
}, [currentDate, data]);

return () => chart.destroy();
}, [currentYear, data]);
useEffect(() => {
let interval;
if (isPlaying) {
interval = setInterval(() => {
setCurrentDate((prevDate) => {
const currentIndex = allDates.indexOf(prevDate);
if (currentIndex === allDates.length - 1) {
setIsPlaying(false);
return prevDate;
}
return allDates[currentIndex + 1];
});
}, 2000);
}
return () => clearInterval(interval);
}, [isPlaying, allDates]);

if (!data) return <LoadingSpinner />;
const handleReset = () => {
setIsPlaying(false);
setCurrentDate(allDates[0]);
};

return (
<div className="w-full max-w-2xl">
<div className="flex gap-4 mb-4">
<button
<div className="w-full max-w-4xl mx-auto p-4">
<div className="mb-4 flex justify-center gap-4">
<button
onClick={() => setIsPlaying(!isPlaying)}
className="px-4 py-2 bg-blue-500 text-white rounded"
className="px-6 py-3 bg-blue-500 text-white rounded-lg text-lg font-semibold hover:bg-blue-600 transition-colors"
>
{isPlaying ? "⏸ Pause" : "▶ Play"}
</button>
<button
onClick={handleReset}
className="px-6 py-3 bg-gray-500 text-white rounded-lg text-lg font-semibold hover:bg-gray-600 transition-colors"
>
{isPlaying ? 'Pause' : 'Play'}
↺ Reset
</button>
<input
type="range"
min="2000"
max="2023"
value={currentYear}
onChange={(e) => setCurrentYear(parseInt(e.target.value))}
className="w-full"
/>
<span>{currentYear}</span>
</div>
<canvas id="timeChartCanvas"></canvas>

<div className="bg-white rounded-lg shadow-lg p-4 h-[600px]">
<canvas ref={chartRef}></canvas>
</div>
</div>
);
}
};

function ChartComponent() {
useEffect(() => {
Expand Down
16 changes: 8 additions & 8 deletions app/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,33 @@ function LandingPage() {

const particles = [];
const numParticles = 60;
const icons = ['⚽', '🏀', '🎾', '⚾', '🏈']; // Sports icons

// Created with Claude 3.5 sonnet
// Initialize particles
// Initialize particles with sports icons
for (let i = 0; i < numParticles; i++) {
particles.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
speed: 0.5 + Math.random(),
angle: Math.random() * Math.PI * 2,
size: 2 + Math.random() * 2
size: 12, // Increased size for icons
icon: icons[Math.floor(Math.random() * icons.length)]
});
}

const render = () => {
ctx.fillStyle = 'rgba(255, 255, 255, 0.05)';
ctx.fillRect(0, 0, canvas.width, canvas.height);

ctx.fillStyle = '#0066cc';
ctx.font = '12px Arial';
particles.forEach(particle => {
// Update position
particle.x += Math.cos(particle.angle) * particle.speed;
particle.y += Math.sin(particle.angle) * particle.speed;

// Draw particle
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
ctx.fill();
// Draw sports icon
ctx.fillStyle = '#0066cc';
ctx.fillText(particle.icon, particle.x, particle.y);

// Connect nearby particles
particles.forEach(other => {
Expand Down
6 changes: 6 additions & 0 deletions public/time_data.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
country,2022-01,2022-02,2022-03,2022-04,2022-05,2022-06,2022-07,2022-08,2022-09,2022-10,2022-11,2022-12,2023-01,2023-02,2023-03,2023-04,2023-05,2023-06
USA,2478,2600,2800,10000,12200,3400,3600,3800,4000,4200,4400,4600,4800,5000,5200,5400,5600,5800
China,5267,5400,5600,5800,6000,6200,6400,6600,6800,7000,7200,7400,7600,7800,8000,8200,8400,8600
Japan,734,750,780,3000,850,900,950,1000,1050,1600,1150,1200,1250,1300,1350,1400,1450,1500
Germany,784,800,4050,900,950,1000,1050,9100,1150,1200,1250,1300,1350,1400,1450,1500,1550,1600
Brazil,433,450,480,500,550,600,650,700,750,800,850,900,14000,1000,1050,1100,1150,1200

0 comments on commit 4e67722

Please sign in to comment.