249 lines
6.6 KiB
JavaScript
249 lines
6.6 KiB
JavaScript
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 © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> 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 = `
|
|
<h1>${buildings[id].name}</h1>
|
|
<p>${msg}</p>
|
|
<canvas id="myChart" height="80"></canvas>
|
|
`
|
|
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()
|
|
}
|
|
|
|
|
|
}
|