2015-05-24 18:52:23 +02:00
|
|
|
Liberapay.charts = {};
|
2013-11-12 15:55:52 -05:00
|
|
|
|
2018-01-09 18:31:36 +01:00
|
|
|
Liberapay.charts.init = function() {
|
2018-11-03 16:16:07 +01:00
|
|
|
$('[data-charts]').each(function () {
|
|
|
|
var url = $(this).data('charts');
|
|
|
|
if (this.tagName == 'BUTTON') {
|
|
|
|
var $container = $($(this).data('charts-container'));
|
|
|
|
$(this).click(function() {
|
|
|
|
$(this).attr('disabled', '').prop('disabled');
|
|
|
|
Liberapay.charts.load(url, $container);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
Liberapay.charts.load(url, $(this));
|
|
|
|
}
|
2018-01-09 18:31:36 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-11-03 16:16:07 +01:00
|
|
|
Liberapay.charts.load = function(url, $container) {
|
2024-05-31 11:26:27 +02:00
|
|
|
fetch(url).then(function(response) {
|
|
|
|
response.json().then(function(series) {
|
|
|
|
$(function() {
|
|
|
|
Liberapay.charts.make(series, $container);
|
|
|
|
});
|
|
|
|
}).catch(Liberapay.error);
|
|
|
|
}).catch(Liberapay.error);
|
2016-03-02 20:47:01 +01:00
|
|
|
}
|
2015-01-07 11:53:34 -05:00
|
|
|
|
2018-11-03 16:16:07 +01:00
|
|
|
Liberapay.charts.make = function(series, $container) {
|
2016-02-13 23:13:41 +01:00
|
|
|
if (series.length) {
|
|
|
|
$('.chart-wrapper').show();
|
|
|
|
} else {
|
2024-05-31 11:26:27 +02:00
|
|
|
if ($container.attr('data-msg-empty')) {
|
|
|
|
$container.append($('<span>').text(' '+$container.attr('data-msg-empty')));
|
2018-11-03 16:16:07 +01:00
|
|
|
}
|
2013-11-12 18:23:57 -05:00
|
|
|
return;
|
|
|
|
}
|
2013-11-12 15:55:52 -05:00
|
|
|
|
2017-11-02 21:16:49 +01:00
|
|
|
function parsePoint(o) {
|
2021-02-05 13:13:42 +01:00
|
|
|
return parseFloat(o ? o.amount || o : 0);
|
2017-11-02 21:16:49 +01:00
|
|
|
}
|
|
|
|
|
2015-01-06 21:15:47 -05:00
|
|
|
// Reverse the series.
|
|
|
|
// ===================
|
2015-01-07 22:27:40 -05:00
|
|
|
// For historical reasons the API is descending when we want ascending.
|
2015-01-06 21:15:47 -05:00
|
|
|
|
|
|
|
series.reverse();
|
|
|
|
|
2013-11-12 22:54:31 -05:00
|
|
|
// Gather charts.
|
|
|
|
// ==============
|
2013-12-12 08:56:31 -05:00
|
|
|
// Sniff the first element to determine what data points are available, and
|
|
|
|
// then search the page for chart containers matching each data point
|
|
|
|
// variable name.
|
2013-11-12 18:23:57 -05:00
|
|
|
|
2015-01-07 22:27:40 -05:00
|
|
|
var charts = Object.keys(series[0]).map(function(name) {
|
|
|
|
return $('[data-chart=' + name + ']');
|
|
|
|
}).filter(function(c) { return c.length });
|
2013-11-12 18:23:57 -05:00
|
|
|
|
2013-11-12 15:55:52 -05:00
|
|
|
|
2015-01-07 22:27:40 -05:00
|
|
|
var H = $('.chart').height() - 20;
|
2017-03-22 11:17:27 +01:00
|
|
|
var W = (1 / series.length).toFixed(10) * $('.chart').width();
|
2017-06-30 18:31:48 +02:00
|
|
|
var skip = 0;
|
|
|
|
if (W < 5) {
|
|
|
|
var keep = Math.floor($('.chart').width() / 5);
|
|
|
|
skip = series.length - keep;
|
|
|
|
series = series.slice(-keep);
|
|
|
|
}
|
2017-03-22 11:17:27 +01:00
|
|
|
W = W > 10 ? '10px' : (W < 5 ? '5px' : Math.floor(W)+'px');
|
2013-11-12 18:23:57 -05:00
|
|
|
|
|
|
|
|
|
|
|
// Compute maxes and scales.
|
|
|
|
// =========================
|
|
|
|
|
2015-01-07 12:37:19 -05:00
|
|
|
var maxes = charts.map(function(chart) {
|
|
|
|
return series.reduce(function(previous, current) {
|
2017-11-02 21:16:49 +01:00
|
|
|
return Math.max(previous, parsePoint(current[chart.data('chart')]));
|
2015-01-07 12:37:19 -05:00
|
|
|
}, 0);
|
|
|
|
});
|
2013-11-12 18:23:57 -05:00
|
|
|
|
2015-01-07 12:37:19 -05:00
|
|
|
var scales = maxes.map(function(max) {
|
|
|
|
return Math.ceil(max / 100) * 100; // round to nearest hundred
|
|
|
|
});
|
2013-11-12 18:23:57 -05:00
|
|
|
|
2015-01-06 16:47:33 -05:00
|
|
|
// Draw bars.
|
|
|
|
// ==========
|
2013-11-12 18:23:57 -05:00
|
|
|
|
2015-01-07 22:27:40 -05:00
|
|
|
charts.forEach(function(chart, chart_index) {
|
2017-06-30 17:15:17 +02:00
|
|
|
chart.css('min-width', (series.length * 5) + 'px');
|
2015-01-07 22:27:40 -05:00
|
|
|
series.forEach(function(point, index) {
|
2017-11-02 21:16:49 +01:00
|
|
|
var y = parsePoint(point[chart.data('chart')]);
|
2015-01-07 22:27:40 -05:00
|
|
|
var bar = $('<div>').addClass('bar');
|
|
|
|
var shaded = $('<div>').addClass('shaded');
|
|
|
|
shaded.html('<span class="y-label">'+ y.toFixed() +'</span>');
|
2018-11-03 16:23:11 +01:00
|
|
|
if (index < series.length / 2) {
|
|
|
|
bar.addClass('left');
|
|
|
|
}
|
2015-01-07 22:27:40 -05:00
|
|
|
bar.append(shaded);
|
|
|
|
|
|
|
|
var xTick = $('<span>').addClass('x-tick');
|
2018-11-03 16:18:48 +01:00
|
|
|
xTick.text(point.date);
|
2015-01-07 22:27:40 -05:00
|
|
|
bar.append(xTick);
|
|
|
|
|
|
|
|
// Display a max flag (only once)
|
|
|
|
if (y === maxes[chart_index] && !chart.data('max-applied')) {
|
|
|
|
bar.addClass('flagged');
|
2018-11-03 16:29:52 +01:00
|
|
|
chart.data('max-applied', true);
|
2015-01-07 22:27:40 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
bar.css('width', W);
|
2015-01-07 23:21:15 -05:00
|
|
|
|
|
|
|
var h = y / scales[chart_index] * H;
|
|
|
|
if (y > 0) h = Math.max(h, 1); // make sure only true 0 is 0 height
|
|
|
|
shaded.css('height', h);
|
2015-01-07 22:27:40 -05:00
|
|
|
|
|
|
|
bar.click(function() {
|
|
|
|
$(this).toggleClass('flagged');
|
|
|
|
});
|
|
|
|
chart.append(bar);
|
2015-01-07 11:51:54 -05:00
|
|
|
});
|
2015-01-07 22:27:40 -05:00
|
|
|
});
|
2013-11-12 15:55:52 -05:00
|
|
|
};
|