aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Étienne Deparis 2023-03-27 20:09:57 +0200
committerGravatar Étienne Deparis 2023-03-28 13:01:10 +0200
commitddfd3170b3e8f76a963b34c896340ba11bb0231c (patch)
treedd42960ba828c7b769da752776f0b89d8ed2bbe2
parent33a90e24345dacf6cf3d58e6c2d10025426057d5 (diff)
downloadbiberons-main.tar.gz
biberons-main.tar.xz
biberons-main.zip
feat: Rewrite the statistics methods and improve graph displayHEADmain
-rw-r--r--lib/biberon/models/baby.rb46
-rw-r--r--lib/biberon/routes/babies.rb4
-rw-r--r--public/js/plot.js219
-rw-r--r--views/babies/graph.slim6
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