Created
December 29, 2023 22:33
-
-
Save MrJonoCES/671aa3ba3e2807a38d16b4b5c7ddaf5e to your computer and use it in GitHub Desktop.
main.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const chartsHome = document.getElementById('prev_monthly_chart_container'); | |
const prevYearData = 'https://raw.githubusercontent.com/stillawake17/FlightViz/main/data/combined_flights_data.json'; | |
const currentYearData = 'https://raw.githubusercontent.com/stillawake17/FlightViz/main/data/combined_flights_data.json'; | |
if (chartsHome) { | |
document.addEventListener('DOMContentLoaded', function() { | |
// Load the data | |
let data2023, data2024; | |
Promise.all([ | |
d3.json(prevYearData).then(data => data2023 = data), | |
d3.json(currentYearData).then(data => data2024 = data) | |
]).then(() => { | |
// Process data for both years | |
[data2023, data2024].forEach(data => { | |
data.forEach(function(d) { | |
let actualTime = null; | |
// Check for arrival data with iataCode 'brs' | |
if (d.arrival && d.arrival.iataCode === 'brs' && 'actualTime' in d.arrival) { | |
actualTime = d.arrival.actualTime; | |
} | |
// If not found in arrival, check for departure data with iataCode 'brs' | |
else if (d.departure && d.departure.iataCode === 'brs' && 'actualTime' in d.departure) { | |
actualTime = d.departure.actualTime; | |
} | |
// If actualTime is found, proceed | |
if (actualTime) { | |
let date = new Date(actualTime); // Parsing ISO 8601 date format | |
d.latestTime = date; | |
d.Hour = date.getHours(); | |
d.Minute = date.getMinutes(); | |
// Classify the time category | |
d.Time_Category = "Regular arrivals"; | |
if (d.Hour === 23 && d.Minute < 30) d.Time_Category = "Shoulder hour flights"; | |
else if ((d.Hour === 23 && d.Minute >= 30) || d.Hour < 6) d.Time_Category = "Night hour arrivals"; | |
else if (d.Hour === 6) d.Time_Category = "Shoulder hour flights"; | |
} | |
}); | |
}); | |
let prev_total_flights = data2023.length; | |
let prev_shoulder_hour_flights = data2023.filter(d => d.Time_Category === 'Shoulder hour flights').length; | |
let prev_night_hour_flights = data2023.filter(d => d.Time_Category === 'Night hour arrivals').length; | |
let current_total_flights = data2024.length; | |
let current_shoulder_hour_flights = data2024.filter(d => d.Time_Category === 'Shoulder hour flights').length; | |
let current_night_hour_flights = data2024.filter(d => d.Time_Category === 'Night hour arrivals').length; | |
// Quotas | |
let quotas = [85990, 9500, 4000]; | |
// Categories and counts | |
let categories = ['Total Flights', 'Shoulder Hour Flights', 'Night Hour Flights']; | |
let counts2023 = [prev_total_flights, prev_shoulder_hour_flights, prev_night_hour_flights]; | |
let counts2024 = [current_total_flights, current_shoulder_hour_flights, current_night_hour_flights]; | |
// Calculating percentages | |
let percentages2023 = counts2023.map((count, index) => (count / quotas[index]) * 100); | |
let percentages2024 = counts2024.map((count, index) => (count / quotas[index]) * 100); | |
// Update dials | |
function formatNumberWithComma(number) { | |
// Check if the number is 1000 or more | |
if (number >= 1000) { | |
// Convert the number to a string | |
const numStr = number.toString(); | |
// Insert a comma before the last three digits | |
return numStr.slice(0, -3) + "," + numStr.slice(-3); | |
} else { | |
// If the number is less than 1000, return it without modification | |
return number.toString(); | |
} | |
} | |
function updateCountElement(elementId, value) { | |
// Select the span element by its ID | |
const countElement = document.getElementById(elementId); | |
// Format the number with a comma | |
const formattedValue = formatNumberWithComma(value); | |
// Update the content of the element | |
countElement.textContent = formattedValue; | |
} | |
// Call this function with the appropriate values | |
updateCountElement("prev_night_count", prev_night_hour_flights); | |
updateCountElement("prev_shoulder_count", prev_shoulder_hour_flights); | |
updateCountElement("prev_total_count", prev_total_flights); | |
updateCountElement("current_night_count", current_night_hour_flights); | |
updateCountElement("current_shoulder_count", current_shoulder_hour_flights); | |
updateCountElement("current_total_count", current_total_flights); | |
// Function to aggregate flight data by month for a given year | |
function aggregateDataByMonth(flightData, year) { | |
const monthlyCounts = new Array(12).fill(0); // Array for each month | |
flightData.forEach(d => { | |
// Assuming the date is in d.arrival.actualTime or d.departure.actualTime | |
let actualTime = d.arrival ? d.arrival.actualTime : (d.departure ? d.departure.actualTime : null); | |
if (actualTime) { | |
let date = new Date(actualTime); | |
let flightYear = date.getFullYear(); | |
let flightMonth = date.getMonth(); // getMonth() returns 0-11 for Jan-Dec | |
if (flightYear === year) { | |
monthlyCounts[flightMonth]++; | |
} | |
} | |
}); | |
return monthlyCounts; | |
} | |
// Assuming percentages is an array with values for total, shoulder, and night flights | |
// and that quotas are the maximum values for each bar | |
// Example: let percentages = [60, 45, 80]; // Percent completion for each category | |
// Define maximum width for the progress bars (could be based on the container's width) | |
const maxBarWidth = 300; // This should match the width of your progress containers | |
// Define colors for each progress bar | |
const barColors = { | |
totalFlights: "#60FF98", | |
shoulderFlights: "#07D7ED", | |
nightFlights: "#7D71FF" | |
}; | |
let roundedPercentages2023 = percentages2023.map(function(d) { | |
return d.toFixed(1); // Rounds the percentage to one decimal place | |
}); | |
let roundedPercentages2024 = percentages2024.map(function(d) { | |
return d.toFixed(1); // Rounds the percentage to one decimal place | |
}); | |
function rotateSingleIndicator(element, percentage) { | |
// Calculate the rotation angle | |
const rotationAngle = percentage / 100 * 270; | |
// animate the rotation of the dial | |
gsap.to(element, { | |
duration: 1, | |
rotation: rotationAngle, | |
transformOrigin: "top right", | |
ease: "power1.inOut" | |
}); | |
// Get the .dial-bg element related to the current indicator | |
const dialBg = element.closest('.dial').querySelector('.dial-bg'); | |
// Calculate the time when the dial reaches 70% | |
const timeToReach70Percent = 0.7 * 1; // Assuming 1 second total duration | |
if (percentage > 80 && percentage < 95) { | |
// Delay the color change to the moment the dial crosses 70% | |
gsap.to(dialBg, { | |
delay: timeToReach70Percent, | |
fill: 'orange' | |
}); | |
} else if (percentage > 95) { | |
// Delay the color change to the moment the dial crosses 70% | |
gsap.to(dialBg, { | |
delay: timeToReach70Percent, | |
fill: 'red' | |
}); | |
} | |
else { | |
// Reset to the original color without delay | |
gsap.to(dialBg, { fill: 'originalColor' }); // Replace 'originalColor' with the default color of the dial | |
} | |
} | |
// Function to rotate all indicators | |
function rotateAllIndicators(selector, percentages) { | |
// Select all elements with the given selector | |
const dials = document.querySelectorAll(selector); | |
// Iterate over each dial element | |
dials.forEach((dial, index) => { | |
// Ensure we don't exceed the length of the percentages array | |
if (index < percentages.length) { | |
const percentage = percentages[index]; | |
// Create a ScrollTrigger for each dial | |
ScrollTrigger.create({ | |
trigger: dial, | |
start: "bottom bottom", // Start the animation when the top of the dial reaches the bottom of the viewport | |
onEnter: () => rotateSingleIndicator(dial.querySelector('.indicator'), percentage) | |
}); | |
} | |
}); | |
} | |
// Rotate the dials for 2023 | |
rotateAllIndicators(".prev_dial", roundedPercentages2023); | |
// Rotate the dials for 2024 | |
rotateAllIndicators(".current_dial", roundedPercentages2024); | |
// Function to draw the bar chart for monthly data | |
function drawMonthlyChart(containerId, monthlyData, category) { | |
// Clear any existing charts | |
d3.select(containerId).selectAll("*").remove(); | |
console.log("Drawing chart for category:", category); | |
console.log("Monthly data:", monthlyData); | |
console.log("Percentages:", [percentages[0], percentages[1], percentages[2]]); | |
// Set up dimensions for the chart | |
// Select the parent container | |
const parentContainer = d3.select('.parent-container').node(); | |
// Get the computed style of the parent container to access padding values | |
const style = window.getComputedStyle(parentContainer); | |
// Parse padding values from the computed style | |
const paddingTop = parseInt(style.paddingTop, 10); | |
const paddingBottom = parseInt(style.paddingBottom, 20); | |
const paddingLeft = parseInt(style.paddingLeft, 10); | |
const paddingRight = parseInt(style.paddingRight, 10); | |
// Calculate the width and height based on the parent container's dimensions and padding | |
const margin = {top: 50, right: 20, bottom: 30, left: 40}; // Adjust margin as needed | |
const width = parentContainer.getBoundingClientRect().width - paddingLeft - paddingRight - margin.left - margin.right; | |
const height = parentContainer.getBoundingClientRect().height - paddingTop - paddingBottom - margin.top - margin.bottom -50; | |
// Define color schemes | |
const colorSchemes = { | |
'Total Flights': '#60FF98', | |
'Shoulder Hour Flights': '#07D7ED', | |
'Night Hour Flights': '#7D71FF' | |
}; | |
// Use the color based on the category | |
const barColor = colorSchemes[category] || 'grey'; // Fallback color if category is not found | |
// Create SVG container for the chart | |
const svg = d3.select(containerId).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 + ")"); | |
// Adding title to the chart | |
svg.append("text") | |
.attr("x", width / 2) | |
.attr("y", 0 - margin.top / 2) | |
.attr("text-anchor", "middle") | |
.style("font-size", "16px") | |
.style("fill", "#fff") | |
// .text(`${category} - Monthly Distribution`) | |
; | |
// Check if SVG is created properly | |
console.log("SVG created with width and height:", width, height); | |
// X scale - months | |
const x = d3.scaleBand() | |
.range([0, width]) | |
.padding(0.1) | |
.domain(monthlyData.map((_, i) => i)); // 0-11 for Jan-Dec | |
// Y scale - flight counts | |
const y = d3.scaleLinear() | |
.range([height, 0]) | |
.domain([0, d3.max(monthlyData)]); | |
// Check scales | |
console.log("X and Y scales set."); | |
// X axis - months | |
svg.append("g") | |
.attr("transform", "translate(0," + height + ")") | |
.call(d3.axisBottom(x).tickFormat(i => d3.timeFormat("%b")(new Date(0, i)))); // Convert month number to 3-letter name | |
// Y axis - flight counts | |
svg.append("g") | |
.call(d3.axisLeft(y)); | |
// Check axes | |
console.log("Axes drawn."); | |
// Bars for the chart | |
svg.selectAll(".bar") | |
.data(monthlyData) | |
.enter() | |
.append("rect") | |
.attr("class", "bar") | |
.attr("x", (d, i) => x(i)) | |
.attr("width", x.bandwidth()) | |
.attr("y", height) // Start the bars at the bottom | |
.attr("height", 0) // Set the initial height to 0 | |
.style("fill", barColor) // Set the color for the bars | |
.each(function(d, i) { | |
// Use GSAP to animate the bars | |
gsap.to(this, { // 'this' refers to the current DOM element | |
attr: { | |
y: y(d), // Animate the 'y' attribute to the final value | |
height: height - y(d), // Animate the 'height' attribute to the final value | |
}, | |
duration: 0.5, | |
delay: i * 0.1, // Add a delay for sequential animation | |
}); | |
}); | |
// Adding data count above each bar | |
svg.selectAll(".text") | |
.data(monthlyData) | |
.enter() | |
.append("text") | |
.attr("class", "label") | |
.attr("fill", "#fff") | |
.attr("x", (d, i) => x(i) + x.bandwidth() / 2) | |
.attr("y", d => y(d) - 5) | |
.attr("text-anchor", "middle") | |
.text(d => d); | |
// Check if bars are appended | |
console.log("Bars should now be visible on the chart."); | |
} | |
// Make sure the full year's flight data is loaded into `flightData` | |
// Click event listeners for the progress bars | |
const prevNightButton = document.getElementById('prev_night_button'); | |
const prevShoulderButton = document.getElementById('prev_shoulder_button'); | |
const prevTotalButton = document.getElementById('prev_total_button'); | |
const currentNightButton = document.getElementById('current_night_button'); | |
const currentShoulderButton = document.getElementById('current_shoulder_button'); | |
const currentTotalButton = document.getElementById('current_total_button'); | |
function attachEventListeners() { | |
d3.select("#prev_total_button").on("click", function() { | |
showMonthlyChartContainer(); // Make sure the container is visible | |
const totalFlightsMonthlyData = aggregateDataByMonth(data, 2023); | |
drawMonthlyChart("#prev_monthly_chart_container", totalFlightsMonthlyData, 'Total Flights'); | |
let element = document.querySelector('.button-on'); | |
if (element) { | |
element.classList.remove('button-on'); | |
} | |
prevTotalButton.classList.add('button-on'); | |
}); | |
d3.select("#prev_shoulder_button").on("click", function() { | |
showMonthlyChartContainer(); // Make sure the container is visible | |
const shoulderFlightsMonthlyData = aggregateDataByMonth( | |
data.filter(d => d.Time_Category === 'Shoulder hour flights' && d.Year === 2023), | |
2023); | |
drawMonthlyChart("#prev_monthly_chart_container", shoulderFlightsMonthlyData, 'Shoulder Hour Flights'); | |
let element = document.querySelector('.button-on'); | |
if (element) { | |
element.classList.remove('button-on'); | |
} | |
prevShoulderButton.classList.add('button-on'); | |
}); | |
d3.select("#prev_night_button").on("click", function() { | |
showMonthlyChartContainer(); // Make sure the container is visible | |
const shoulderFlightsMonthlyData = aggregateDataByMonth( | |
data.filter(d => d.Time_Category === 'Night hour arrivals' && d.Year === 2023), | |
2023); | |
drawMonthlyChart("#prev_monthly_chart_container", shoulderFlightsMonthlyData, 'Night hour arrivals'); | |
let element = document.querySelector('.button-on'); | |
if (element) { | |
element.classList.remove('button-on'); | |
} | |
prevNightButton.classList.add('button-on'); | |
}); | |
d3.select("#current_total_button").on("click", function() { | |
showMonthlyChartContainer(); // Make sure the container is visible | |
const totalFlightsMonthlyData = aggregateDataByMonth(data, 2024); | |
drawMonthlyChart("#current_monthly_chart_container", totalFlightsMonthlyData, 'Total Flights'); | |
let element = document.querySelector('.button-on'); | |
if (element) { | |
element.classList.remove('button-on'); | |
} | |
currentTotalButton.classList.add('button-on'); | |
}); | |
d3.select("#current_shoulder_button").on("click", function() { | |
showMonthlyChartContainer(); // Make sure the container is visible | |
const shoulderFlightsMonthlyData = aggregateDataByMonth( | |
data.filter(d => d.Time_Category === 'Shoulder hour flights' && d.Year === 2024), | |
2024); | |
drawMonthlyChart("#current_monthly_chart_container", shoulderFlightsMonthlyData, 'Shoulder Hour Flights'); | |
let element = document.querySelector('.button-on'); | |
if (element) { | |
element.classList.remove('button-on'); | |
} | |
currentShoulderButton.classList.add('button-on'); | |
}); | |
d3.select("#current_night_button").on("click", function() { | |
showMonthlyChartContainer(); // Make sure the container is visible | |
const shoulderFlightsMonthlyData = aggregateDataByMonth( | |
data.filter(d => d.Time_Category === 'Night hour arrivals' && d.Year === 2024), | |
2024); | |
drawMonthlyChart("#current_monthly_chart_container", shoulderFlightsMonthlyData, 'Night hour arrivals'); | |
let element = document.querySelector('.button-on'); | |
if (element) { | |
element.classList.remove('button-on'); | |
} | |
currentNightButton.classList.add('button-on'); | |
}); | |
} | |
function loadTotalFlightsChart(year, containerId, button) { | |
showMonthlyChartContainer(containerId); // Make sure the container is visible | |
const nightFlightsMonthlyData = aggregateDataByMonth(data, year); | |
drawMonthlyChart(containerId, nightFlightsMonthlyData, 'Night Hour Flights'); | |
button.classList.add('button-on'); | |
} | |
// Call the function to attach the event listeners | |
attachEventListeners(); | |
// Call the function to load the charts for each year | |
loadTotalFlightsChart(2023, "#prev_monthly_chart_container", prevNightButton); | |
loadTotalFlightsChart(2024, "#current_monthly_chart_container", currentNightButton); | |
}).catch(function(error) { | |
console.error("Error loading the data:", error); | |
}) | |
function showMonthlyChartContainer(containerId) { | |
// Display the container | |
d3.select(containerId).style("display", "block"); | |
} | |
// Call the function to show the containers for each year | |
showMonthlyChartContainer("#prev_monthly_chart_container"); | |
showMonthlyChartContainer("#current_monthly_chart_container"); | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment