diff options
author | 2023-03-27 20:09:57 +0200 | |
---|---|---|
committer | 2023-03-28 13:01:10 +0200 | |
commit | ddfd3170b3e8f76a963b34c896340ba11bb0231c (patch) | |
tree | dd42960ba828c7b769da752776f0b89d8ed2bbe2 | |
parent | 33a90e24345dacf6cf3d58e6c2d10025426057d5 (diff) | |
download | biberons-main.tar.gz biberons-main.tar.xz biberons-main.zip |
-rw-r--r-- | lib/biberon/models/baby.rb | 46 | ||||
-rw-r--r-- | lib/biberon/routes/babies.rb | 4 | ||||
-rw-r--r-- | public/js/plot.js | 219 | ||||
-rw-r--r-- | views/babies/graph.slim | 6 |
4 files changed, 147 insertions, 128 deletions
diff --git a/lib/biberon/models/baby.rb b/lib/biberon/models/baby.rb index dbf9f0e..4a5053d 100644 --- a/lib/biberon/models/baby.rb +++ b/lib/biberon/models/baby.rb @@ -38,18 +38,44 @@ module Biberon bibs_of_day(date).sum(:quantity) || 0 end - def statistics(first_day = first_biberon_date, last_day = nil) - return [] unless first_day - last_day ||= last_biberon_date || Date.today + def statistics(which = :drinked, first_day = nil, last_day = nil) + today = Date.today + first_day ||= first_biberon_date || today + last_day ||= last_biberon_date || today + timeframe = first_day.to_time...(last_day + 1).to_time + raw_data = send("#{which}_statistics", timeframe).to_h do |stat| + [stat[:day], stat[:number]] + end first_day.upto(last_day).each.map do |date| - bibs = bibs_of_day(date) - { - 'day' => date.strftime('%Y%m%d'), - 'bibs' => bibs.where(Sequel.~(quantity: nil)).count, - 'feces' => bibs.where(feces: true).count, - 'total_drinked' => total_drinked_on(date) - } + date_s = date.to_s + { 'day' => date_s, 'number' => raw_data[date_s] || 0 } end end + + private + + def drinked_statistics(timeframe) + statistics_base('SUM(quantity)', timeframe).all + end + + def bibs_statistics(timeframe) + statistics_base( + 'COUNT(id)', timeframe + ).where(Sequel.~(quantity: nil)).all + end + + def feces_statistics(timeframe) + statistics_base( + 'COUNT(id)', timeframe + ).where(feces: true).all + end + + def statistics_base(numbering, timeframe) + biberons_dataset.select( + # cast to char to be compatible with sqlite and mariadb + Sequel.lit('CAST(DATE(datetime) AS CHAR) AS day'), + Sequel.lit("#{numbering} AS number") + ).where(datetime: timeframe).group_by(:day) + end end end diff --git a/lib/biberon/routes/babies.rb b/lib/biberon/routes/babies.rb index 63f04b0..9a49cde 100644 --- a/lib/biberon/routes/babies.rb +++ b/lib/biberon/routes/babies.rb @@ -35,13 +35,13 @@ module Biberon routing.redirect "/babies/#{@baby.id}" end - routing.get 'json' do + routing.on 'data', String do |stat| response['Content-Type'] = 'application/json' window = [ App.parse_date(request.params['since']), App.parse_date(request.params['until']) ].compact - @baby.statistics(*window).to_json + @baby.statistics(stat.to_sym, *window).to_json end routing.get('graph') { view 'babies/graph' } diff --git a/public/js/plot.js b/public/js/plot.js index 7e53f00..2f74642 100644 --- a/public/js/plot.js +++ b/public/js/plot.js @@ -1,141 +1,97 @@ -var drinked_graph = null, - bibs_graph = null, - poop_graph = null; +var biberons_graphs = { + drinked: { + graph: null, + label: 'Quantity (ml)', + color: '#9b4dca', + max: 0 + }, + bibs: { + graph: null, + label: 'Bibs number', + color: '#5cb3ff', + max: 8 + }, + poops: { + graph: null, + label: '💩', + color: '#6f4e37', + max: 4 + } +}; function zero_to_null(data) { + // Only keep the last one to 0 to avoid missing trailing data + last_item = data.pop(); data.forEach(function(item, idx, data) { - if (item['bibs'] == 0) item['bibs'] = null; - if (item['feces'] == 0) item['feces'] = null; - if (item['total_drinked'] == 0) item['total_drinked'] = null; + if (item['number'] == 0) item['number'] = null; data[idx] = item; }); + data.push(last_item); return data; } -function update_graphs() { +function isInViewport(elem) { + const bounding = elem.getBoundingClientRect(); + const screenHeight = window.innerHeight || document.documentElement.clientHeight; + return Math.abs(bounding.top) <= screenHeight; +} + +function data_url(graph_name) { + if (graph_name == 'poops') graph_name = 'feces'; + const since = document.getElementById('since').value; const until = document.getElementById('until').value; - fetch(`json?since=${since}&until=${until}`).then(function(response) { + return `data/${graph_name}?since=${since}&until=${until}`; +} + +function update_graph(graph_name) { + var graphOpts = biberons_graphs[graph_name] + + var graph = graphOpts['graph']; + // Avoid to spam the server when the graph is just to be created + if (graph === null || graph === true) return; + + const url = data_url(graph_name); + // Avoid to spam the server when the same graph has already be drawn + if (graphOpts['last_url'] == url) return; + graphOpts['last_url'] = url; + + fetch(url).then(function(response) { response.json().then(function(bibData) { - if (drinked_graph) { - drinked_graph.data.datasets[0].data = bibData; - drinked_graph.update(); - } - if (bibs_graph) { - bibs_graph.data.datasets[0].data = bibData; - bibs_graph.update(); - } - if (poop_graph) { - poop_graph.data.datasets[0].data = bibData; - poop_graph.update(); - } + graph.data.datasets[0].data = zero_to_null(bibData); + graph.update(); }); }); } -function draw_graphs() { - fetch('json').then(function(response) { - let gridColor = '#eee'; - if (window.matchMedia("(prefers-color-scheme: dark)").matches) { - Chart.defaults.color = '#d1d1d1'; - gridColor = '#606c76'; - } - Chart.defaults.scales.linear.min = 0; - Chart.overrides.line.spanGaps = true; - +function draw_graph(graph_name) { + var graphOpts = biberons_graphs[graph_name]; + // avoid it to be created several times + graphOpts['graph'] = true; + const url = data_url(graph_name); + graphOpts['last_url'] = url; + return fetch(url).then(function(response) { response.json().then(function(bibData) { - const drinked_ctx = document.getElementById('drinkedBibs'); + const graph_ctx = document.getElementById(`${graph_name}Graph`); bibData = zero_to_null(bibData); - drinked_graph = new Chart(drinked_ctx, { - type: 'line', - data: { - datasets: [{ - label: 'Quantity (ml)', - borderColor: '#9b4dca', - backgroundColor: '#9b4dca', - /*backgroundColor: '#d789ff',*/ - data: bibData - }] - }, - options: { - parsing: { - xAxisKey: 'day', - yAxisKey: 'total_drinked' - }, - scales: { - y: { - grid: { - color: gridColor - } - }, - x: { - grid: { - color: gridColor - } - } - } - } - }); - const bibs_ctx = document.getElementById('bibs'); - bibs_graph = new Chart(bibs_ctx, { - type: 'line', - data: { - datasets: [{ - label: 'Bibs number', - borderColor: '#5cb3ff', - backgroundColor: '#5cb3ff', - data: bibData - }] - }, - options: { - parsing: { - xAxisKey: 'day', - yAxisKey: 'bibs' - }, - scales: { - y: { - grid: { - color: gridColor - }, - suggestedMax: 8 - }, - x: { - grid: { - color: gridColor - } - } - } - } - }); - const poop_ctx = document.getElementById('poops'); - poop_graph = new Chart(poop_ctx, { + graphOpts['graph'] = new Chart(graph_ctx, { type: 'line', data: { datasets: [{ - label: '💩', - borderColor: '#6f4e37', - backgroundColor: '#6f4e37', + label: graphOpts['label'], + borderColor: graphOpts['color'], + backgroundColor: graphOpts['color'], data: bibData }] }, options: { parsing: { xAxisKey: 'day', - yAxisKey: 'feces' + yAxisKey: 'number' }, scales: { - y: { - grid: { - color: gridColor - }, - suggestedMax: 4 - }, - x: { - grid: { - color: gridColor - } - } + y: { suggestedMax: graphOpts['max'] } } } }); @@ -143,7 +99,44 @@ function draw_graphs() { }); } -document.addEventListener('DOMContentLoaded', draw_graphs, false); -document.getElementById('reset').addEventListener( - 'click', update_graphs, false -); +function update_all_graphs(event) { + for (graph_name in biberons_graphs) { + const elem = document.getElementById(`${graph_name}Graph`); + if (isInViewport(elem)) { + update_graph(graph_name); + } + } + event.target.blur(); +} + +function lazy_draw_graphs() { + for (graph_name in biberons_graphs) { + const elem = document.getElementById(`${graph_name}Graph`); + if (isInViewport(elem)) { + if (biberons_graphs[graph_name]['graph'] === null) { + draw_graph(graph_name); + } else { + update_graph(graph_name); + } + } + } +} + +function init_graphs() { + let gridColor = '#eee'; + if (window.matchMedia('(prefers-color-scheme: dark)').matches) { + Chart.defaults.color = '#d1d1d1'; + Chart.defaults.borderColor = '#606c76'; + } + Chart.defaults.scales.linear.min = 0; + Chart.overrides.line.spanGaps = true; + + draw_graph('drinked'); + + document.addEventListener('scroll', lazy_draw_graphs, false); + document.getElementById('reset').addEventListener( + 'click', update_all_graphs, false + ); +} + +document.addEventListener('DOMContentLoaded', init_graphs, false); diff --git a/views/babies/graph.slim b/views/babies/graph.slim index 7f5ba49..79969a7 100644 --- a/views/babies/graph.slim +++ b/views/babies/graph.slim @@ -10,9 +10,9 @@ h1 = @baby.name button#reset.button< = R18n.t.graph.filter -canvas#drinkedBibs.chart -canvas#bibs.chart -canvas#poops.chart +canvas#drinkedGraph.chart +canvas#bibsGraph.chart +canvas#poopsGraph.chart p.center-text a.button.button-outline href="/babies/#{@baby.id}" = R18n.t.graph.back_to_bibs |