#import "@preview/lilaq:0.6.0" as lq #let epoch = datetime(year: 1970, month: 01, day: 01, hour: 0, minute: 0, second: 0) #let unix-to-datetime(stamp) = epoch + duration(seconds: int(stamp)) #let datetime-to-unix(dt) = int((dt - epoch).seconds()) #let parse-iso-datetime(s) = { let parts = s.split("T") let date-part = parts.at(0).split("-").map(int) let time-part = parts.at(1).split(":").map(int) datetime( year: date-part.at(0), month: date-part.at(1), day: date-part.at(2), hour: time-part.at(0), minute: time-part.at(1), second: time-part.at(2) ) } #let window-regions(filtered-windows, filtered-times-dt) = { let regions = () let i = 0 while i < filtered-windows.len() { let start-time = filtered-times-dt.at(i) let status = filtered-windows.at(i) let j = i // Find end of consecutive region with same status while j < filtered-windows.len() and filtered-windows.at(j) == status { j += 1 } let end-time = if j < filtered-times-dt.len() { filtered-times-dt.at(j - 1) } else { filtered-times-dt.at(filtered-times-dt.len() - 1) } // Add small duration to end time to make rectangle visible let end-time-dt = end-time + duration(seconds: 60) let fill-color = if status == 0 { none } else { rgb("#e8e8e8") } regions.push(lq.rect( start-time, 0%, width: end-time-dt - start-time, height: 100%, fill: fill-color, stroke: none, z-index: 1 )) i = j } regions } #let plot_co2(path, start-date, stop-date) = [ #let start-unix = datetime-to-unix(start-date) #let stop-unix = datetime-to-unix(stop-date) #let data = lq.load-txt( read(path), header: true, converters: (time: parse-iso-datetime, rest: float) ) #let times = data.at("time") #let co2s = data.at("co2") #let windows = data.at("windows") // Filter data by date range #let filtered-indices = range(times.len()).filter(i => { let t = times.at(i) t >= start-date and t <= stop-date }) #let filtered-times-dt = filtered-indices.map(i => times.at(i)) #let filtered-co2s = filtered-indices.map(i => co2s.at(i)) #let filtered-windows = filtered-indices.map(i => windows.at(i)) #let regions = window-regions(filtered-windows, filtered-times-dt) #lq.diagram( width: 100%, height: 7cm, title: [CO2 Level over Time], xlabel: [Time], ylabel: [CO2 Level (ppm)], yaxis: (lim: (400, 1500), exponent: 0), xaxis: (format-ticks: lq.tick-format.datetime.with( format-offset: (datetimes, period: none) => none, )), ..regions, lq.plot(filtered-times-dt, filtered-co2s, label: [CO2 Level], color: rgb("#388e3c")) ) ] #let plot_temp_hum(path, start-date, stop-date) = [ #let start-unix = datetime-to-unix(start-date) #let stop-unix = datetime-to-unix(stop-date) #let data = lq.load-txt( read(path), header: true, converters: (time: parse-iso-datetime, rest: float) ) #let times = data.at("time") #let temps = data.at("temperature") #let hums = data.at("humidity") #let windows = data.at("windows") // Filter data by date range #let filtered-indices = range(times.len()).filter(i => { let t = times.at(i) t >= start-date and t <= stop-date }) #let filtered-times-dt = filtered-indices.map(i => times.at(i)) #let filtered-temps = filtered-indices.map(i => temps.at(i)) #let filtered-hums = filtered-indices.map(i => hums.at(i)) #let filtered-times-dt = filtered-indices.map(i => times.at(i)) #let filtered-windows = filtered-indices.map(i => windows.at(i)) #let regions = window-regions(filtered-windows, filtered-times-dt) #lq.diagram( width: 100%, height: 7cm, title: [Temperature and Humidity over Time], xlabel: [Time], ylabel: [Temperature (°C)], yaxis: (lim: (20, 35)), xaxis: (format-ticks: lq.tick-format.datetime.with( format-offset: (datetimes, period: none) => none, )), ..regions, lq.plot(filtered-times-dt, filtered-temps, label: [Temperature], color: rgb("#d32f2f")), lq.yaxis( position: right, label: [Humidity (%)], lim: (30, 55), lq.plot(filtered-times-dt, filtered-hums, label: [Humidity], color: rgb("#1976d2")) ) ) ]