diff --git a/INFO-3168 (JS 2)/Project/LeviMclean_nasa-meteorite-explorer/css/style.css b/INFO-3168 (JS 2)/Project/LeviMclean_nasa-meteorite-explorer/css/style.css index 34637dc..7eb6b69 100644 --- a/INFO-3168 (JS 2)/Project/LeviMclean_nasa-meteorite-explorer/css/style.css +++ b/INFO-3168 (JS 2)/Project/LeviMclean_nasa-meteorite-explorer/css/style.css @@ -82,6 +82,7 @@ table td, table th { table th { background-color: #f0f0f0; + cursor: pointer; } table tr:nth-child(even) { /* Make every even row a different colour */ @@ -98,4 +99,32 @@ table tr:hover { height: 700px; width: 90%; margin: 20px auto; +} + +/* Arrow Styling */ +.arrow { + margin-left: 4px; + font-size: 0.8rem; +} + +/* Small mobile responsiveness changes */ +@media (max-width: 625px) { + fieldset { + display: flex; + flex-direction: column; + gap: 8px; + margin: 10px; + } + + /* Make inputs full width on mobile */ + fieldset input { + max-width: 100%; + width: 100%; + box-sizing: border-box; + } + + /* Buttons stack nicely on mobile */ + fieldset button { + width: 100%; + } } \ No newline at end of file diff --git a/INFO-3168 (JS 2)/Project/LeviMclean_nasa-meteorite-explorer/index.html b/INFO-3168 (JS 2)/Project/LeviMclean_nasa-meteorite-explorer/index.html index 9a9dd3f..eaff70a 100644 --- a/INFO-3168 (JS 2)/Project/LeviMclean_nasa-meteorite-explorer/index.html +++ b/INFO-3168 (JS 2)/Project/LeviMclean_nasa-meteorite-explorer/index.html @@ -23,46 +23,47 @@
Sort By Year - + - + + + - -
Sort By Name - + - -

+ + + + +

- +
- - - - - + + + + +
Meteorite Data Table
IDNameYearRecclassMassID Name Year Recclass Mass

- - - + diff --git a/INFO-3168 (JS 2)/Project/LeviMclean_nasa-meteorite-explorer/js/dataHandler.js b/INFO-3168 (JS 2)/Project/LeviMclean_nasa-meteorite-explorer/js/dataHandler.js index 6da8d80..0366f5d 100644 --- a/INFO-3168 (JS 2)/Project/LeviMclean_nasa-meteorite-explorer/js/dataHandler.js +++ b/INFO-3168 (JS 2)/Project/LeviMclean_nasa-meteorite-explorer/js/dataHandler.js @@ -1,4 +1,134 @@ -// TODO: -// Implement Search by year and name -// Reset filters button -// Clickable table headers for sortin (reuse code?) \ No newline at end of file +let currentSortColumn = -1; +let ascending = true; +let filteredData = []; + +// Clickable table headers for sorting +function sortTable(col) { + const table = document.getElementById("meteorTable"); + const tbody = table.tBodies[0]; + const rows = Array.from(tbody.rows); + + // If the column that was clicked is the same as the previous one clicked, reverse sort direction + if (col === currentSortColumn) { + ascending = !ascending; + } else { + ascending = true; + currentSortColumn = col; + } + + // Update arrows for column clicked on + updateArrows(col); + + // Actual sorting of the table rows + rows.sort((rowA, rowB) => { + let a = rowA.cells[col].textContent.trim(); + let b = rowB.cells[col].textContent.trim(); + + let numA = Number(a); + let numB = Number(b); + + if (!isNaN(numA) && !isNaN(numB)) { + return ascending ? numA - numB : numB - numA; + } + + if (a < b) return ascending ? -1 : 1; + if (a > b) return ascending ? 1 : -1; + return 0; + }); + + // Append sorted rows to table body + for (let row of rows) { + tbody.appendChild(row); + } +} + +// Function to swap arrow symbols on click +function updateArrows(col) { + const arrows = document.querySelectorAll("th .arrow"); + + arrows.forEach(arrow => arrow.textContent = ""); + + arrows[col].textContent = ascending ? "▲" : "▼"; +} + +// Function to sort data by Name +function filterByName() { + // Get the user input in all lowercase + const input = document.getElementById("nameInput").value.trim().toLowerCase(); + + // Use the spread operator to turn the meteorData into an array, then filter by input + if (input === "") { + filteredData = [...meteorData]; + } else { + filteredData = meteorData.filter(meteor => + meteor.name && meteor.name.toLowerCase().includes(input) + ); + } + + // Redraw markers with only the filtered data + drawMarkers(filteredData); +} + +// Function to sort by year +function filterByYear() { + // Get min and max year inputs + const minYear = Number(document.getElementById("minYearInput").value); + const maxYear = Number(document.getElementById("maxYearInput").value); + + // Once again, filter the meteorData, this time within a range of minYear-maxYear if present + filteredData = meteorData.filter(meteor => { + const year = parseInt(meteor.year); + if (isNaN(year)) return false; + + if (!isNaN(minYear) && year < minYear) return false; + if (!isNaN(maxYear) && year > maxYear) return true; + + return true; + }); + + // And redraw filtered data by year + drawMarkers(filteredData); +} + +// Function to reset filters +function resetFilters() { + filteredData = [...meteorData]; + drawMarkers(filteredData); // Just reload the map without any sorting +} + +// Function to download filtered data +function downloadData() { + // Do nothing if data hasn't been modified + if (!filteredData || filteredData.length === 0) { + alert("No data to download."); + return; + } + + // Create a string of JSON from the filtered data + const jsonString = JSON.stringify(filteredData, null, 2); + + // Create a binary blob from the JSON + const blob = new Blob([jsonString], {type: "application/json" }); + + // Create a URL from the blob object + const url = URL.createObjectURL(blob); + + // Bit hacky, but I create an element for the download link + const a = document.createElement("a"); + + // Set the Href of to the URL of the blob + a.href = url; + + // Set the downloaded file name for the JSON + a.download = "filtered_meteor_data.json"; + + // Append the to the document + document.body.appendChild(a); + + // Click the element + a.click(); + + // Remove the element and revoke the URL object + document.body.removeChild(a); + URL.revokeObjectURL(url); +} \ No newline at end of file diff --git a/INFO-3168 (JS 2)/Project/LeviMclean_nasa-meteorite-explorer/js/main.js b/INFO-3168 (JS 2)/Project/LeviMclean_nasa-meteorite-explorer/js/main.js index c670a3d..44a8d2e 100644 --- a/INFO-3168 (JS 2)/Project/LeviMclean_nasa-meteorite-explorer/js/main.js +++ b/INFO-3168 (JS 2)/Project/LeviMclean_nasa-meteorite-explorer/js/main.js @@ -1,22 +1,25 @@ -fetch('./js/Meteorite_Landings.json') - .then((response) => response.json()) - .then(data => { - console.log(data); - const tableBody = document.getElementById("meteorTableBody"); - data.splice(0,500).forEach(meteor => { // Just get 500 values for now - const row = document.createElement("tr"); - const id = document.createElement("td"); - id.textContent = meteor.id ?? "-"; - const name = document.createElement("td"); - name.textContent = meteor.name ?? "-"; - const year = document.createElement("td"); - year.textContent = meteor.year ?? "-"; - const recclass = document.createElement("td"); - recclass.textContent = meteor.recclass ?? "-"; - const mass = document.createElement("td"); - mass.textContent = meteor["mass (g)"] + "g" ?? "-"; - row.append(id, name, year, recclass, mass); - tableBody.appendChild(row); +// Fetch the JSON data and put it into the table body +function fetchJson() { + fetch('./js/Meteorite_Landings.json') + .then((response) => response.json()) + .then(data => { + console.log(data); + const tableBody = document.getElementById("meteorTableBody"); + data.splice(0,500).forEach(meteor => { // Just get 500 values for now + const row = document.createElement("tr"); + const id = document.createElement("td"); + id.textContent = meteor.id ?? "-"; + const name = document.createElement("td"); + name.textContent = meteor.name ?? "-"; + const year = document.createElement("td"); + year.textContent = meteor.year ?? "-"; + const recclass = document.createElement("td"); + recclass.textContent = meteor.recclass ?? "-"; + const mass = document.createElement("td"); + mass.textContent = meteor["mass (g)"] + "g" ?? "-"; + row.append(id, name, year, recclass, mass); + tableBody.appendChild(row); + }) }) - }) - .catch(error => console.error(error)); \ No newline at end of file + .catch(error => console.error(error)); +} \ No newline at end of file diff --git a/INFO-3168 (JS 2)/Project/LeviMclean_nasa-meteorite-explorer/js/map.js b/INFO-3168 (JS 2)/Project/LeviMclean_nasa-meteorite-explorer/js/map.js index 7079405..d250f1c 100644 --- a/INFO-3168 (JS 2)/Project/LeviMclean_nasa-meteorite-explorer/js/map.js +++ b/INFO-3168 (JS 2)/Project/LeviMclean_nasa-meteorite-explorer/js/map.js @@ -1,39 +1,64 @@ +let map; +let meteorData = []; +let markers = []; + // Load the map from the Google Maps JS API function loadMap() { - const map = new google.maps.Map(document.getElementById("map"), { + map = new google.maps.Map(document.getElementById("map"), { center: {lat: 0, lng: 0}, // Center of the globe zoom: 2 }); - const infoWindow = new google.maps.InfoWindow(); // create InfoWindow object to use later - - // Add custom markers from meteor data fetch("./js/Meteorite_Landings.json") .then((response) => response.json()) .then(data => { - data.splice(0,500).forEach(location => { - const marker = new google.maps.Marker({ - position: { lat: location.reclat, lng: location.reclong}, - map: map, - title: location.name - }); - - marker.addListener("click", () => { // Open and show the InfoWindow on click - const content = ` -
-

${location.name}

-

Mass: ${location["mass (g)"]} g

-

Year: ${location.year}

-

Class: ${location.recclass}

-

Fall Status: ${location.fall}

-

Recorded Latitude: ${location.reclat}

-

Recorded Longitude: ${location.reclong}

-
`; // By doing this I have more control over what I put in the InfoWindow and how to style it - infoWindow.setContent(content); - infoWindow.open(map, marker); - }); - }); + meteorData = data.splice(0,500); + filteredData = [...meteorData]; + drawMarkers(filteredData); }) .catch(error => console.error(error)); }; +function drawMarkers(data) { + clearMarkers(); + const infoWindow = new google.maps.InfoWindow(); // create InfoWindow object to use later + + // Add custom markers from meteor data + data.forEach(location => { + const lat = Number(location.reclat); + const lng = Number(location.reclong); + + // Ignore entries that are not numbers (will cause errors) + if (isNaN(lat) || isNaN(lng)) return; + + // Create marker from reclat and reclong + const marker = new google.maps.Marker({ + position: { lat: lat, lng: lng}, + map: map, + title: location.name + }); + + marker.addListener("click", () => { // Open and show the InfoWindow on click + const content = ` +
+

${location.name}

+

Mass: ${location["mass (g)"]} g

+

Year: ${location.year}

+

Class: ${location.recclass}

+

Fall Status: ${location.fall}

+

Recorded Latitude: ${lat}

+

Recorded Longitude: ${lng}

+
`; // By doing this I have more control over what I put in the InfoWindow and how to style it + infoWindow.setContent(content); + infoWindow.open(map, marker); + }); + + markers.push(marker); + }); +} + +// Function to clear markers for use in drawMarkers() +function clearMarkers() { + markers.forEach(marker => marker.setMap(null)); + markers = []; +} \ No newline at end of file