const buildings = { "MC": { name: "McDermott Library", bounds: [ [32.98732, -96.74801], [32.98732, -96.74723], [32.98660, -96.74723], [32.98660, -96.74801] ], }, "SU": { name: "Student Union", bounds: [ [32.98711, -96.74909], [32.98711, -96.74848], [32.98661, -96.74849], [32.98661, -96.74910] ] }, "ECSW": { name: "Engineering and Computer Science West", bounds: [ [32.98639, -96.75217], [32.98638, -96.75128], [32.98579, -96.75119], [32.98581, -96.75217] ] } } const data = getData() const map = L.map('map', { minZoom: 15, maxZoom: 18 }).setView([32.98613, -96.75042], 18) const STEP = 2 * 60 * 1000 const INTERVAL = 10 * 60 * 1000 + STEP L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw', { maxZoom: 18, attribution: 'Map data © OpenStreetMap contributors', id: 'mapbox/dark-v10', // id: 'mapbox/streets-v11', tileSize: 512, zoomOffset: -1 }).addTo(map) for (const [id, building] of Object.entries(buildings)) { // console.log(id, building) const poly = L.polygon(building.bounds) poly.on('click', ev => { // console.log(ev) map.panTo(poly.getCenter()) displayCard(id) }).addTo(map) poly.setStyle({ color: '#727272', opacity: .5 }) buildings[id].poly = poly } // Update map colors every minute drawRegions() let chart = null let currentChart = null setInterval(() => { drawRegions() if (chart) { updateChart(currentChart) } }, 60 * 1000) async function drawRegions() { const data = await getData() const ecsw = data.filter(d => d.loc === 'ECSW' && Date.now() - d.time < INTERVAL) console.log(ecsw) const avg = ecsw.reduce((acc, v) => acc + v.count, 0) / ecsw.length const current = ecsw[ecsw.length - 1]?.count ?? NaN const delta = current - avg if (Number.isNaN(delta)) return const min = Math.min(...data.map(d => d.count)) const max = Math.max(...data.map(d => d.count)) if (max == min) return const red = new Color(255, 0, 0) const green = new Color(0, 255, 0) console.log({current, max, min}) const color = green.interpolate(red, (current - min) / (max - min)) buildings.ECSW.poly.setStyle({ color: color.asRgbCss() }) console.log({avg, current, delta, color}) } class Color { constructor(r, g, b) { this.r = Math.max(0, Math.min(255, r)) this.g = Math.max(0, Math.min(255, g)) this.b = Math.max(0, Math.min(255, b)) } asRgbCss() { return "rgb("+this.r+", "+this.g+", "+this.b+")" } interpolate(other, ratio) { return new Color( this.r + (other.r - this.r) * ratio, this.g + (other.g - this.g) * ratio, this.b + (other.b - this.b) * ratio ) } } function getData() { return fetch('/db.csv').then(res => res.text()).then(text => { return text.trim().split('\n').map(line => { const [loc, time, count] = line.split(',') return { loc, time: new Date(+time), count: parseInt(count) } }) }) } function lerp(v0, v1, x) { return (1 - x) * v0 + x * v1 } async function displayCard(id) { const data = (await getData()).filter(d => d.loc === id && Date.now() - d.time < INTERVAL) const card = document.getElementById("card") await card.animate([ { transform: 'translateY(50px)', opacity: "0" }, ], { duration: 200, fill: 'forwards', easing: 'cubic-bezier(0.4, 0.0, 1, 1)' }).finished console.log(data) const current = data[data.length - 1]?.count ?? NaN const avg = data.reduce((acc, v) => acc + v.count, 0) / data.length const delta = current - avg const busy = 0 < delta console.log({busy, avg, current, delta}) const msg = Number.isNaN(delta) ? "No data..." : (busy ? "Busier than usual": "Not as busy :)") card.innerHTML = `
${msg}
` card.style.setProperty("--region", `'${id}'`) chart = null updateChart(id) await card.animate([ { transform: 'translateY(0%)', opacity: "1" }, ], { duration: 200, fill: 'forwards', easing: 'cubic-bezier(0.0, 0.0, 0.2, 1)' }).finished } async function updateChart(id) { currentChart = id const parsed = (await getData()) .filter(d => d.loc === id && Date.now() - d.time < INTERVAL) .map(d => { // this is 0-5 let ago = Math.floor((Date.now() - d.time) / STEP) // if (20 < ago) ago = 20 return {time: d.time, ago, count: d.count} }).reduce((acc, v) => { if (!acc[v.ago]) { acc[v.ago] = [] } acc[v.ago].push(v.count) return acc }, Array.from({ length: 6 })).map(v => { if (typeof v === 'undefined') return NaN return v.reduce((acc, v) => acc + v, 0) / v.length }) const canvas = document.getElementById('myChart') canvas.innerHTML = canvas.innerHTML const ctx = canvas.getContext('2d') const min = STEP / 1000 / 60 const labels = [ 'Now', `${min}m ago`, `${min * 2}m ago`, `${min * 3}m ago`, `${min * 4}m ago`, `${min * 5}m ago` ].reverse() if (!chart) { chart = new Chart(ctx, { type: 'line', data: { labels, datasets: [{ label: 'Density', data: parsed.slice(0, labels.length).reverse(), borderColor: '#55aadd', backgroundColor: '#55aadd', // borderWidth: 2 }] }, options: { scales: { x: { beginAtZero: true, }, y: { beginAtZero: true, } } } }) } else { chart.data.datasets = [{ label: 'Density', data: parsed.slice(0, labels.length).reverse(), borderColor: '#55aadd', backgroundColor: '#55aadd', // borderWidth: 2 }] chart.update() } }