2019-11-02 00:06:25 +01:00
|
|
|
const font_14pt = {
|
2017-08-29 01:12:55 +02:00
|
|
|
family: 'Source Sans Pro',
|
2017-02-08 02:24:31 +01:00
|
|
|
size: 14,
|
|
|
|
color: '#000000',
|
|
|
|
};
|
2017-06-30 21:37:59 +02:00
|
|
|
|
2019-12-06 09:35:13 +01:00
|
|
|
let last_full_update = Infinity;
|
2017-02-08 02:24:31 +01:00
|
|
|
|
2017-01-11 21:44:59 +01:00
|
|
|
// TODO: should take a dict of arrays and do it for all keys
|
|
|
|
function partial_sums(array) {
|
2019-11-02 00:06:25 +01:00
|
|
|
let accumulator = 0;
|
2020-07-02 01:45:54 +02:00
|
|
|
return array.map((o) => {
|
2017-06-30 21:37:59 +02:00
|
|
|
accumulator += o;
|
|
|
|
return accumulator;
|
|
|
|
});
|
2017-01-11 21:44:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Assumes date is a round number of hours
|
|
|
|
function floor_to_local_day(date) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const date_copy = new Date(date.getTime());
|
2017-01-11 21:44:59 +01:00
|
|
|
date_copy.setHours(0);
|
|
|
|
return date_copy;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Assumes date is a round number of hours
|
|
|
|
function floor_to_local_week(date) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const date_copy = floor_to_local_day(date);
|
2017-01-11 21:44:59 +01:00
|
|
|
date_copy.setHours(-24 * date.getDay());
|
|
|
|
return date_copy;
|
|
|
|
}
|
|
|
|
|
|
|
|
function format_date(date, include_hour) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const months = [
|
2018-05-07 01:38:14 +02:00
|
|
|
i18n.t('January'),
|
|
|
|
i18n.t('February'),
|
|
|
|
i18n.t('March'),
|
|
|
|
i18n.t('April'),
|
|
|
|
i18n.t('May'),
|
|
|
|
i18n.t('June'),
|
|
|
|
i18n.t('July'),
|
|
|
|
i18n.t('August'),
|
|
|
|
i18n.t('September'),
|
|
|
|
i18n.t('October'),
|
|
|
|
i18n.t('November'),
|
|
|
|
i18n.t('December'),
|
|
|
|
];
|
2019-11-02 00:06:25 +01:00
|
|
|
const month_str = months[date.getMonth()];
|
|
|
|
const year = date.getFullYear();
|
|
|
|
const day = date.getDate();
|
2017-01-11 21:44:59 +01:00
|
|
|
if (include_hour) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const hour = date.getHours();
|
2017-06-30 21:37:59 +02:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const str = hour >= 12 ? "PM" : "AM";
|
2017-06-30 21:37:59 +02:00
|
|
|
|
2018-06-06 18:50:09 +02:00
|
|
|
return month_str + " " + day + ", " + hour % 12 + ":00" + str;
|
2017-01-11 21:44:59 +01:00
|
|
|
}
|
2017-02-02 01:07:41 +01:00
|
|
|
return month_str + ' ' + day + ', ' + year;
|
2017-01-11 21:44:59 +01:00
|
|
|
}
|
|
|
|
|
2017-03-07 05:50:26 +01:00
|
|
|
function update_last_full_update(end_times) {
|
|
|
|
if (end_times.length === 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
last_full_update = Math.min(last_full_update, end_times[end_times.length - 1]);
|
2019-11-02 00:06:25 +01:00
|
|
|
const update_time = new Date(last_full_update * 1000);
|
|
|
|
const locale_date = update_time.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
|
|
|
|
const locale_time = update_time.toLocaleTimeString().replace(":00 ", " ");
|
2017-03-07 05:50:26 +01:00
|
|
|
|
|
|
|
$('#id_last_full_update').text(locale_time + " on " + locale_date);
|
|
|
|
$('#id_last_full_update').closest('.last-update').show();
|
|
|
|
}
|
|
|
|
|
2020-07-02 01:45:54 +02:00
|
|
|
$(() => {
|
2017-03-07 05:50:26 +01:00
|
|
|
$('span[data-toggle="tooltip"]').tooltip({
|
|
|
|
animation: false,
|
|
|
|
placement: 'top',
|
|
|
|
trigger: 'manual',
|
|
|
|
});
|
2020-07-02 01:45:54 +02:00
|
|
|
$('#id_last_update_question_sign').hover(() => {
|
2018-01-03 20:48:56 +01:00
|
|
|
$('span.last_update_tooltip').tooltip('toggle');
|
2017-03-07 05:50:26 +01:00
|
|
|
});
|
2018-01-03 20:48:56 +01:00
|
|
|
// Add configuration for any additional tooltips here.
|
2017-03-07 05:50:26 +01:00
|
|
|
});
|
|
|
|
|
2017-02-06 09:32:07 +01:00
|
|
|
function populate_messages_sent_over_time(data) {
|
|
|
|
if (data.end_times.length === 0) {
|
|
|
|
// TODO: do something nicer here
|
|
|
|
return;
|
|
|
|
}
|
2016-12-20 02:30:08 +01:00
|
|
|
|
2017-02-06 09:32:07 +01:00
|
|
|
// Helper functions
|
|
|
|
function make_traces(dates, values, type, date_formatter) {
|
2020-07-02 01:45:54 +02:00
|
|
|
const text = dates.map((date) => date_formatter(date));
|
2019-11-02 00:06:25 +01:00
|
|
|
const common = { x: dates, type: type, hoverinfo: 'none', text: text };
|
2017-02-06 09:32:07 +01:00
|
|
|
return {
|
2020-02-09 04:06:28 +01:00
|
|
|
human: { // 5062a0
|
|
|
|
name: i18n.t("Humans"), y: values.human, marker: {color: '#5f6ea0'},
|
|
|
|
...common,
|
|
|
|
},
|
|
|
|
bot: { // a09b5f bbb56e
|
|
|
|
name: i18n.t("Bots"), y: values.bot, marker: {color: '#b7b867'},
|
|
|
|
...common,
|
|
|
|
},
|
|
|
|
me: {
|
|
|
|
name: i18n.t("Me"), y: values.me, marker: {color: '#be6d68'},
|
|
|
|
...common,
|
|
|
|
},
|
2017-02-06 09:32:07 +01:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const layout = {
|
2017-01-11 21:44:59 +01:00
|
|
|
barmode: 'group',
|
|
|
|
width: 750,
|
|
|
|
height: 400,
|
2017-02-06 09:32:07 +01:00
|
|
|
margin: { l: 40, r: 0, b: 40, t: 0 },
|
2016-12-20 02:30:08 +01:00
|
|
|
xaxis: {
|
2017-01-11 21:44:59 +01:00
|
|
|
fixedrange: true,
|
2017-02-06 09:32:07 +01:00
|
|
|
rangeslider: { bordercolor: '#D8D8D8', borderwidth: 1 },
|
2016-12-20 02:30:08 +01:00
|
|
|
type: 'date',
|
|
|
|
},
|
2017-02-06 09:32:07 +01:00
|
|
|
yaxis: { fixedrange: true, rangemode: 'tozero' },
|
2017-01-11 21:44:59 +01:00
|
|
|
legend: {
|
2017-03-21 04:19:52 +01:00
|
|
|
x: 0.62, y: 1.12, orientation: 'h', font: font_14pt,
|
2017-01-11 21:44:59 +01:00
|
|
|
},
|
2017-02-08 02:24:31 +01:00
|
|
|
font: font_14pt,
|
2017-01-11 21:44:59 +01:00
|
|
|
};
|
|
|
|
|
2017-02-06 09:32:07 +01:00
|
|
|
function make_rangeselector(x, y, button1, button2) {
|
2018-05-07 01:38:14 +02:00
|
|
|
return {x: x, y: y,
|
|
|
|
buttons: [
|
2020-02-09 04:06:28 +01:00
|
|
|
{stepmode: 'backward', ...button1},
|
|
|
|
{stepmode: 'backward', ...button2},
|
2018-05-07 01:38:14 +02:00
|
|
|
{step: 'all', label: 'All time'}]};
|
2017-02-06 09:32:07 +01:00
|
|
|
}
|
2017-09-17 00:57:41 +02:00
|
|
|
|
2017-02-06 09:32:07 +01:00
|
|
|
// This is also the cumulative rangeselector
|
2019-11-02 00:06:25 +01:00
|
|
|
const daily_rangeselector = make_rangeselector(
|
2017-02-06 09:32:07 +01:00
|
|
|
0.68, -0.62,
|
2017-07-10 13:06:56 +02:00
|
|
|
{count: 10, label: i18n.t('Last 10 days'), step: 'day'},
|
|
|
|
{count: 30, label: i18n.t('Last 30 days'), step: 'day'});
|
2019-11-02 00:06:25 +01:00
|
|
|
const weekly_rangeselector = make_rangeselector(
|
2017-02-06 09:32:07 +01:00
|
|
|
0.656, -0.62,
|
2017-07-10 13:06:56 +02:00
|
|
|
{count: 2, label: i18n.t('Last 2 months'), step: 'month'},
|
|
|
|
{count: 6, label: i18n.t('Last 6 months'), step: 'month'});
|
2017-02-02 01:07:41 +01:00
|
|
|
|
2017-02-06 09:32:07 +01:00
|
|
|
function add_hover_handler() {
|
2020-07-02 01:45:54 +02:00
|
|
|
document.getElementById('id_messages_sent_over_time').on('plotly_hover', (data) => {
|
2017-06-30 21:37:59 +02:00
|
|
|
$("#hoverinfo").show();
|
2017-02-06 09:32:07 +01:00
|
|
|
document.getElementById('hover_date').innerText =
|
|
|
|
data.points[0].data.text[data.points[0].pointNumber];
|
2019-11-02 00:06:25 +01:00
|
|
|
const values = [null, null, null];
|
2020-07-02 01:45:54 +02:00
|
|
|
data.points.forEach((trace) => {
|
2017-02-06 09:32:07 +01:00
|
|
|
values[trace.curveNumber] = trace.y;
|
|
|
|
});
|
2019-11-02 00:06:25 +01:00
|
|
|
const hover_text_ids = ['hover_me', 'hover_human', 'hover_bot'];
|
|
|
|
const hover_value_ids = ['hover_me_value', 'hover_human_value', 'hover_bot_value'];
|
|
|
|
for (let i = 0; i < values.length; i += 1) {
|
2017-03-21 04:19:52 +01:00
|
|
|
if (values[i] !== null) {
|
|
|
|
document.getElementById(hover_text_ids[i]).style.display = 'inline';
|
|
|
|
document.getElementById(hover_value_ids[i]).style.display = 'inline';
|
|
|
|
document.getElementById(hover_value_ids[i]).innerText = values[i];
|
|
|
|
} else {
|
|
|
|
document.getElementById(hover_text_ids[i]).style.display = 'none';
|
|
|
|
document.getElementById(hover_value_ids[i]).style.display = 'none';
|
|
|
|
}
|
2017-02-06 09:32:07 +01:00
|
|
|
}
|
|
|
|
});
|
2017-01-11 21:44:59 +01:00
|
|
|
}
|
|
|
|
|
2020-07-02 01:45:54 +02:00
|
|
|
const start_dates = data.end_times.map((timestamp) =>
|
2017-01-11 21:44:59 +01:00
|
|
|
// data.end_times are the ends of hour long intervals.
|
2020-07-02 01:45:54 +02:00
|
|
|
new Date(timestamp * 1000 - 60 * 60 * 1000)
|
|
|
|
);
|
2017-01-11 21:44:59 +01:00
|
|
|
|
|
|
|
function aggregate_data(aggregation) {
|
2019-11-02 00:06:25 +01:00
|
|
|
let start;
|
|
|
|
let is_boundary;
|
2017-01-11 21:44:59 +01:00
|
|
|
if (aggregation === 'day') {
|
|
|
|
start = floor_to_local_day(start_dates[0]);
|
|
|
|
is_boundary = function (date) {
|
|
|
|
return date.getHours() === 0;
|
|
|
|
};
|
|
|
|
} else if (aggregation === 'week') {
|
|
|
|
start = floor_to_local_week(start_dates[0]);
|
|
|
|
is_boundary = function (date) {
|
|
|
|
return date.getHours() === 0 && date.getDay() === 0;
|
|
|
|
};
|
|
|
|
}
|
2019-11-02 00:06:25 +01:00
|
|
|
const dates = [start];
|
|
|
|
const values = {human: [], bot: [], me: []};
|
|
|
|
let current = {human: 0, bot: 0, me: 0};
|
|
|
|
let i_init = 0;
|
2017-01-11 21:44:59 +01:00
|
|
|
if (is_boundary(start_dates[0])) {
|
2018-05-19 00:44:30 +02:00
|
|
|
current = {human: data.everyone.human[0],
|
|
|
|
bot: data.everyone.bot[0],
|
|
|
|
me: data.user.human[0]};
|
2017-01-11 21:44:59 +01:00
|
|
|
i_init = 1;
|
|
|
|
}
|
2019-11-02 00:06:25 +01:00
|
|
|
for (let i = i_init; i < start_dates.length; i += 1) {
|
2017-01-11 21:44:59 +01:00
|
|
|
if (is_boundary(start_dates[i])) {
|
|
|
|
dates.push(start_dates[i]);
|
|
|
|
values.human.push(current.human);
|
|
|
|
values.bot.push(current.bot);
|
2017-03-21 04:19:52 +01:00
|
|
|
values.me.push(current.me);
|
|
|
|
current = {human: 0, bot: 0, me: 0};
|
2017-01-11 21:44:59 +01:00
|
|
|
}
|
2018-05-18 22:13:08 +02:00
|
|
|
current.human += data.everyone.human[i];
|
|
|
|
current.bot += data.everyone.bot[i];
|
2017-03-21 04:19:52 +01:00
|
|
|
current.me += data.user.human[i];
|
2017-01-11 21:44:59 +01:00
|
|
|
}
|
|
|
|
values.human.push(current.human);
|
|
|
|
values.bot.push(current.bot);
|
2017-03-21 04:19:52 +01:00
|
|
|
values.me.push(current.me);
|
2017-02-06 09:32:07 +01:00
|
|
|
return {
|
|
|
|
dates: dates, values: values,
|
|
|
|
last_value_is_partial: !is_boundary(new Date(
|
2018-06-04 21:13:07 +02:00
|
|
|
start_dates[start_dates.length - 1].getTime() + 60 * 60 * 1000))};
|
2017-01-11 21:44:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Generate traces
|
2019-11-02 00:06:25 +01:00
|
|
|
let date_formatter = function (date) {
|
2017-01-11 21:44:59 +01:00
|
|
|
return format_date(date, true);
|
|
|
|
};
|
2019-11-02 00:06:25 +01:00
|
|
|
let values = {me: data.user.human, human: data.everyone.human, bot: data.everyone.bot};
|
2017-01-11 21:44:59 +01:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
let info = aggregate_data('day');
|
2017-01-11 21:44:59 +01:00
|
|
|
date_formatter = function (date) {
|
|
|
|
return format_date(date, false);
|
|
|
|
};
|
2019-11-02 00:06:25 +01:00
|
|
|
const last_day_is_partial = info.last_value_is_partial;
|
|
|
|
const daily_traces = make_traces(info.dates, info.values, 'bar', date_formatter);
|
2017-01-11 21:44:59 +01:00
|
|
|
|
|
|
|
info = aggregate_data('week');
|
|
|
|
date_formatter = function (date) {
|
2017-09-05 09:00:32 +02:00
|
|
|
return i18n.t("Week of __date__", {date: format_date(date, false)});
|
2017-01-11 21:44:59 +01:00
|
|
|
};
|
2019-11-02 00:06:25 +01:00
|
|
|
const last_week_is_partial = info.last_value_is_partial;
|
|
|
|
const weekly_traces = make_traces(info.dates, info.values, 'bar', date_formatter);
|
2017-01-11 21:44:59 +01:00
|
|
|
|
2020-07-02 01:45:54 +02:00
|
|
|
const dates = data.end_times.map((timestamp) => new Date(timestamp * 1000));
|
2018-05-18 22:13:08 +02:00
|
|
|
values = {human: partial_sums(data.everyone.human), bot: partial_sums(data.everyone.bot),
|
2018-05-07 01:38:14 +02:00
|
|
|
me: partial_sums(data.user.human)};
|
2017-01-11 21:44:59 +01:00
|
|
|
date_formatter = function (date) {
|
|
|
|
return format_date(date, true);
|
|
|
|
};
|
2019-11-02 00:06:25 +01:00
|
|
|
const cumulative_traces = make_traces(dates, values, 'scatter', date_formatter);
|
2017-01-11 21:44:59 +01:00
|
|
|
|
2017-02-06 09:32:07 +01:00
|
|
|
// Functions to draw and interact with the plot
|
2017-01-11 21:44:59 +01:00
|
|
|
|
2017-02-06 09:32:07 +01:00
|
|
|
// We need to redraw plot entirely if switching from (the cumulative) line
|
|
|
|
// graph to any bar graph, since otherwise the rangeselector shows both (plotly bug)
|
2019-11-02 00:06:25 +01:00
|
|
|
let clicked_cumulative = false;
|
2017-01-11 21:44:59 +01:00
|
|
|
|
2017-02-06 09:32:07 +01:00
|
|
|
function draw_or_update_plot(rangeselector, traces, last_value_is_partial, initial_draw) {
|
2017-09-17 00:57:41 +02:00
|
|
|
$('#daily_button, #weekly_button, #cumulative_button').removeClass("selected");
|
2017-12-03 12:21:05 +01:00
|
|
|
$('#id_messages_sent_over_time > div').removeClass("spinner");
|
2017-02-08 04:40:54 +01:00
|
|
|
if (initial_draw) {
|
|
|
|
traces.human.visible = true;
|
|
|
|
traces.bot.visible = 'legendonly';
|
2017-03-21 04:19:52 +01:00
|
|
|
traces.me.visible = 'legendonly';
|
2017-02-08 04:40:54 +01:00
|
|
|
} else {
|
2019-11-02 00:06:25 +01:00
|
|
|
const plotDiv = document.getElementById('id_messages_sent_over_time');
|
2017-03-21 04:19:52 +01:00
|
|
|
traces.me.visible = plotDiv.data[0].visible;
|
|
|
|
traces.human.visible = plotDiv.data[1].visible;
|
|
|
|
traces.bot.visible = plotDiv.data[2].visible;
|
2017-02-08 04:40:54 +01:00
|
|
|
}
|
2017-01-11 21:44:59 +01:00
|
|
|
layout.xaxis.rangeselector = rangeselector;
|
2017-02-06 09:32:07 +01:00
|
|
|
if (clicked_cumulative || initial_draw) {
|
2017-01-11 21:44:59 +01:00
|
|
|
Plotly.newPlot('id_messages_sent_over_time',
|
2017-03-21 04:19:52 +01:00
|
|
|
[traces.me, traces.human, traces.bot], layout, {displayModeBar: false});
|
2017-02-06 09:32:07 +01:00
|
|
|
add_hover_handler();
|
2017-01-11 21:44:59 +01:00
|
|
|
} else {
|
2017-03-21 04:19:52 +01:00
|
|
|
Plotly.deleteTraces('id_messages_sent_over_time', [0, 1, 2]);
|
|
|
|
Plotly.addTraces('id_messages_sent_over_time', [traces.me, traces.human, traces.bot]);
|
2017-01-11 21:44:59 +01:00
|
|
|
Plotly.relayout('id_messages_sent_over_time', layout);
|
|
|
|
}
|
2017-02-06 09:32:07 +01:00
|
|
|
$('#id_messages_sent_over_time').attr('last_value_is_partial', last_value_is_partial);
|
2017-01-11 21:44:59 +01:00
|
|
|
}
|
|
|
|
|
2017-02-06 09:32:07 +01:00
|
|
|
// Click handlers for aggregation buttons
|
2017-01-11 21:44:59 +01:00
|
|
|
$('#daily_button').click(function () {
|
2017-02-06 09:32:07 +01:00
|
|
|
draw_or_update_plot(daily_rangeselector, daily_traces, last_day_is_partial, false);
|
2017-06-30 21:37:59 +02:00
|
|
|
$(this).addClass("selected");
|
2017-01-11 21:44:59 +01:00
|
|
|
clicked_cumulative = false;
|
|
|
|
});
|
|
|
|
|
|
|
|
$('#weekly_button').click(function () {
|
2017-02-06 09:32:07 +01:00
|
|
|
draw_or_update_plot(weekly_rangeselector, weekly_traces, last_week_is_partial, false);
|
2017-06-30 21:37:59 +02:00
|
|
|
$(this).addClass("selected");
|
2017-01-11 21:44:59 +01:00
|
|
|
clicked_cumulative = false;
|
|
|
|
});
|
|
|
|
|
|
|
|
$('#cumulative_button').click(function () {
|
|
|
|
clicked_cumulative = false;
|
2017-02-06 09:32:07 +01:00
|
|
|
draw_or_update_plot(daily_rangeselector, cumulative_traces, false, false);
|
2017-06-30 21:37:59 +02:00
|
|
|
$(this).addClass("selected");
|
2017-01-11 21:44:59 +01:00
|
|
|
clicked_cumulative = true;
|
|
|
|
});
|
2017-02-02 01:07:41 +01:00
|
|
|
|
2017-02-06 09:32:07 +01:00
|
|
|
// Initial drawing of plot
|
|
|
|
if (weekly_traces.human.x.length < 12) {
|
|
|
|
draw_or_update_plot(daily_rangeselector, daily_traces, last_day_is_partial, true);
|
2017-06-30 21:37:59 +02:00
|
|
|
$('#daily_button').addClass("selected");
|
2017-02-06 09:32:07 +01:00
|
|
|
} else {
|
|
|
|
draw_or_update_plot(weekly_rangeselector, weekly_traces, last_week_is_partial, true);
|
2017-06-30 21:37:59 +02:00
|
|
|
$('#weekly_button').addClass("selected");
|
2017-02-06 09:32:07 +01:00
|
|
|
}
|
2017-01-11 21:44:59 +01:00
|
|
|
}
|
|
|
|
|
2017-02-07 23:30:34 +01:00
|
|
|
function round_to_percentages(values, total) {
|
2020-07-02 01:45:54 +02:00
|
|
|
return values.map((x) => {
|
2017-02-08 06:00:04 +01:00
|
|
|
if (x === total) {
|
|
|
|
return '100%';
|
|
|
|
}
|
2017-02-09 09:03:25 +01:00
|
|
|
if (x === 0) {
|
|
|
|
return '0%';
|
|
|
|
}
|
2019-11-02 00:06:25 +01:00
|
|
|
const unrounded = x / total * 100;
|
2017-06-30 21:37:59 +02:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const precision = Math.min(
|
2017-06-30 21:37:59 +02:00
|
|
|
6, // this is the max precision (two #, 4 decimal points; 99.9999%).
|
|
|
|
Math.max(
|
|
|
|
2, // the minimum amount of precision (40% or 6.0%).
|
|
|
|
Math.floor(-Math.log10(100 - unrounded)) + 3
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
2017-02-07 23:30:34 +01:00
|
|
|
return unrounded.toPrecision(precision) + '%';
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-02-10 00:39:42 +01:00
|
|
|
// Last label will turn into "Other" if time_series data has a label not in labels
|
|
|
|
function compute_summary_chart_data(time_series_data, num_steps, labels_) {
|
2020-02-12 07:46:27 +01:00
|
|
|
const data = new Map();
|
2020-05-27 00:50:02 +02:00
|
|
|
for (const [key, array] of Object.entries(time_series_data)) {
|
|
|
|
if (array.length < num_steps) {
|
|
|
|
num_steps = array.length;
|
2017-02-03 03:16:19 +01:00
|
|
|
}
|
2019-11-02 00:06:25 +01:00
|
|
|
let sum = 0;
|
|
|
|
for (let i = 1; i <= num_steps; i += 1) {
|
2020-05-27 00:50:02 +02:00
|
|
|
sum += array[array.length - i];
|
2017-02-03 03:16:19 +01:00
|
|
|
}
|
2020-02-12 07:46:27 +01:00
|
|
|
data.set(key, sum);
|
2017-01-11 21:44:59 +01:00
|
|
|
}
|
2019-11-02 00:06:25 +01:00
|
|
|
const labels = labels_.slice();
|
|
|
|
const values = [];
|
2020-07-02 01:45:54 +02:00
|
|
|
labels.forEach((label) => {
|
2020-02-12 07:46:27 +01:00
|
|
|
if (data.has(label)) {
|
|
|
|
values.push(data.get(label));
|
|
|
|
data.delete(label);
|
2017-02-10 00:39:42 +01:00
|
|
|
} else {
|
|
|
|
values.push(0);
|
2017-02-03 03:16:19 +01:00
|
|
|
}
|
2017-02-10 00:39:42 +01:00
|
|
|
});
|
2020-02-12 07:46:27 +01:00
|
|
|
if (data.size !== 0) {
|
2018-06-04 21:13:07 +02:00
|
|
|
labels[labels.length - 1] = "Other";
|
2020-03-03 00:24:25 +01:00
|
|
|
for (const sum of data.values()) {
|
2020-02-12 07:46:27 +01:00
|
|
|
values[labels.length - 1] += sum;
|
2017-02-02 10:20:53 +01:00
|
|
|
}
|
|
|
|
}
|
2020-07-02 01:45:54 +02:00
|
|
|
const total = values.reduce((a, b) => a + b, 0);
|
2017-02-07 23:30:34 +01:00
|
|
|
return {
|
2017-02-09 03:38:30 +01:00
|
|
|
values: values,
|
|
|
|
labels: labels,
|
2017-02-09 09:03:25 +01:00
|
|
|
percentages: round_to_percentages(values, total),
|
2017-02-09 03:38:30 +01:00
|
|
|
total: total,
|
2017-02-07 23:30:34 +01:00
|
|
|
};
|
2017-01-11 21:44:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function populate_messages_sent_by_client(data) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const layout = {
|
2017-02-09 09:03:25 +01:00
|
|
|
width: 750,
|
|
|
|
height: null, // set in draw_plot()
|
|
|
|
margin: { l: 3, r: 40, b: 40, t: 0 },
|
2017-02-08 02:24:31 +01:00
|
|
|
font: font_14pt,
|
2017-02-09 09:03:25 +01:00
|
|
|
xaxis: { range: null }, // set in draw_plot()
|
|
|
|
yaxis: { showticklabels: false },
|
|
|
|
showlegend: false,
|
2017-02-07 23:30:34 +01:00
|
|
|
};
|
|
|
|
|
2017-02-10 00:39:42 +01:00
|
|
|
// sort labels so that values are descending in the default view
|
2019-11-02 00:06:25 +01:00
|
|
|
const everyone_month = compute_summary_chart_data(
|
2018-05-19 00:44:30 +02:00
|
|
|
data.everyone, 30, data.display_order.slice(0, 12));
|
2019-11-02 00:06:25 +01:00
|
|
|
const label_values = [];
|
|
|
|
for (let i = 0; i < everyone_month.values.length; i += 1) {
|
2017-02-10 00:39:42 +01:00
|
|
|
label_values.push({
|
2018-05-18 22:13:08 +02:00
|
|
|
label: everyone_month.labels[i],
|
|
|
|
value: everyone_month.labels[i] === "Other" ? -1 : everyone_month.values[i],
|
2017-02-10 00:39:42 +01:00
|
|
|
});
|
|
|
|
}
|
2020-07-02 01:45:54 +02:00
|
|
|
label_values.sort((a, b) => b.value - a.value);
|
2019-11-02 00:06:25 +01:00
|
|
|
const labels = [];
|
2020-07-02 01:45:54 +02:00
|
|
|
label_values.forEach((item) => { labels.push(item.label); });
|
2017-02-10 00:39:42 +01:00
|
|
|
|
2017-02-09 03:38:30 +01:00
|
|
|
function make_plot_data(time_series_data, num_steps) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const plot_data = compute_summary_chart_data(time_series_data, num_steps, labels);
|
2017-02-09 09:03:25 +01:00
|
|
|
plot_data.values.reverse();
|
|
|
|
plot_data.labels.reverse();
|
|
|
|
plot_data.percentages.reverse();
|
2019-11-02 00:06:25 +01:00
|
|
|
const annotations = {values: [], labels: [], text: []};
|
|
|
|
for (let i = 0; i < plot_data.values.length; i += 1) {
|
2017-02-10 00:39:42 +01:00
|
|
|
if (plot_data.values[i] > 0) {
|
|
|
|
annotations.values.push(plot_data.values[i]);
|
|
|
|
annotations.labels.push(plot_data.labels[i]);
|
|
|
|
annotations.text.push(' ' + plot_data.labels[i] + ' (' + plot_data.percentages[i] + ')');
|
|
|
|
}
|
2017-02-09 09:03:25 +01:00
|
|
|
}
|
2017-02-09 03:38:30 +01:00
|
|
|
return {
|
|
|
|
trace: {
|
2017-02-09 09:03:25 +01:00
|
|
|
x: plot_data.values,
|
|
|
|
y: plot_data.labels,
|
|
|
|
type: 'bar',
|
|
|
|
orientation: 'h',
|
2017-02-09 03:38:30 +01:00
|
|
|
sort: false,
|
|
|
|
textinfo: "text",
|
2017-02-09 09:03:25 +01:00
|
|
|
hoverinfo: "none",
|
|
|
|
marker: { color: '#537c5e' },
|
2017-08-29 01:12:55 +02:00
|
|
|
font: { family: 'Source Sans Pro', size: 18, color: '#000000' },
|
2017-02-09 09:03:25 +01:00
|
|
|
},
|
|
|
|
trace_annotations: {
|
2017-02-10 00:39:42 +01:00
|
|
|
x: annotations.values,
|
|
|
|
y: annotations.labels,
|
2017-02-09 09:03:25 +01:00
|
|
|
mode: 'text',
|
|
|
|
type: 'scatter',
|
|
|
|
textposition: 'middle right',
|
2017-02-10 00:39:42 +01:00
|
|
|
text: annotations.text,
|
2017-02-09 03:38:30 +01:00
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const plot_data = {
|
2018-05-18 22:13:08 +02:00
|
|
|
everyone: {
|
|
|
|
cumulative: make_plot_data(data.everyone, data.end_times.length),
|
|
|
|
year: make_plot_data(data.everyone, 365),
|
|
|
|
month: make_plot_data(data.everyone, 30),
|
|
|
|
week: make_plot_data(data.everyone, 7),
|
2017-02-07 23:30:34 +01:00
|
|
|
},
|
|
|
|
user: {
|
2017-02-09 03:38:30 +01:00
|
|
|
cumulative: make_plot_data(data.user, data.end_times.length),
|
2017-03-05 11:44:35 +01:00
|
|
|
year: make_plot_data(data.user, 365),
|
|
|
|
month: make_plot_data(data.user, 30),
|
|
|
|
week: make_plot_data(data.user, 7),
|
2017-02-02 01:07:41 +01:00
|
|
|
},
|
2017-01-11 21:44:59 +01:00
|
|
|
};
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
let user_button = 'everyone';
|
|
|
|
let time_button;
|
2017-03-05 11:44:35 +01:00
|
|
|
if (data.end_times.length >= 30) {
|
|
|
|
time_button = 'month';
|
2017-06-30 21:37:59 +02:00
|
|
|
$('#messages_by_client_last_month_button').addClass("selected");
|
2017-03-05 11:44:35 +01:00
|
|
|
} else {
|
|
|
|
time_button = 'cumulative';
|
2017-06-30 21:37:59 +02:00
|
|
|
$('#messages_by_client_cumulative_button').addClass("selected");
|
2017-03-05 11:44:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (data.end_times.length < 365) {
|
2017-06-30 21:37:59 +02:00
|
|
|
$("#pie_messages_sent_by_client button[data-time='year']").remove();
|
2017-03-05 11:44:35 +01:00
|
|
|
if (data.end_times.length < 30) {
|
2017-06-30 21:37:59 +02:00
|
|
|
$("#pie_messages_sent_by_client button[data-time='month']").remove();
|
2017-03-05 11:44:35 +01:00
|
|
|
if (data.end_times.length < 7) {
|
2017-06-30 21:37:59 +02:00
|
|
|
$("#pie_messages_sent_by_client button[data-time='week']").remove();
|
2017-03-05 11:44:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-01-11 21:44:59 +01:00
|
|
|
|
2017-02-07 23:30:34 +01:00
|
|
|
function draw_plot() {
|
2017-12-03 12:21:05 +01:00
|
|
|
$('#id_messages_sent_by_client > div').removeClass("spinner");
|
2019-11-02 00:06:25 +01:00
|
|
|
const data_ = plot_data[user_button][time_button];
|
2017-02-09 09:03:25 +01:00
|
|
|
layout.height = layout.margin.b + data_.trace.x.length * 30;
|
2020-02-12 01:35:16 +01:00
|
|
|
layout.xaxis.range = [0, Math.max(...data_.trace.x) * 1.3];
|
2017-02-07 23:30:34 +01:00
|
|
|
Plotly.newPlot('id_messages_sent_by_client',
|
2017-02-09 09:03:25 +01:00
|
|
|
[data_.trace, data_.trace_annotations],
|
2017-02-07 23:30:34 +01:00
|
|
|
layout,
|
2017-02-09 09:03:25 +01:00
|
|
|
{displayModeBar: false, staticPlot: true});
|
2017-02-07 23:30:34 +01:00
|
|
|
}
|
2017-01-11 21:44:59 +01:00
|
|
|
|
2017-02-07 23:30:34 +01:00
|
|
|
draw_plot();
|
|
|
|
|
|
|
|
// Click handlers
|
|
|
|
function set_user_button(button) {
|
2017-06-30 21:37:59 +02:00
|
|
|
$("#pie_messages_sent_by_client button[data-user]").removeClass("selected");
|
|
|
|
button.addClass("selected");
|
2017-02-07 23:30:34 +01:00
|
|
|
}
|
2017-01-11 21:44:59 +01:00
|
|
|
|
2017-02-07 23:30:34 +01:00
|
|
|
function set_time_button(button) {
|
2017-06-30 21:37:59 +02:00
|
|
|
$("#pie_messages_sent_by_client button[data-time]").removeClass("selected");
|
|
|
|
button.addClass("selected");
|
2017-02-07 23:30:34 +01:00
|
|
|
}
|
|
|
|
|
2017-06-30 21:37:59 +02:00
|
|
|
$("#pie_messages_sent_by_client button").click(function () {
|
|
|
|
if ($(this).attr("data-user")) {
|
|
|
|
set_user_button($(this));
|
|
|
|
user_button = $(this).attr("data-user");
|
|
|
|
}
|
|
|
|
if ($(this).attr("data-time")) {
|
|
|
|
set_time_button($(this));
|
|
|
|
time_button = $(this).attr("data-time");
|
|
|
|
}
|
2017-02-07 23:30:34 +01:00
|
|
|
draw_plot();
|
2017-01-11 21:44:59 +01:00
|
|
|
});
|
2017-02-07 23:30:34 +01:00
|
|
|
|
2017-01-11 21:44:59 +01:00
|
|
|
// handle links with @href started with '#' only
|
|
|
|
$(document).on('click', 'a[href^="#"]', function (e) {
|
|
|
|
// target element id
|
2019-11-02 00:06:25 +01:00
|
|
|
const id = $(this).attr('href');
|
2017-01-11 21:44:59 +01:00
|
|
|
// target element
|
2019-11-02 00:06:25 +01:00
|
|
|
const $id = $(id);
|
2017-01-11 21:44:59 +01:00
|
|
|
if ($id.length === 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// prevent standard hash navigation (avoid blinking in IE)
|
|
|
|
e.preventDefault();
|
2019-11-02 00:06:25 +01:00
|
|
|
const pos = $id.offset().top + $('.page-content')[0].scrollTop - 50;
|
2017-01-11 21:44:59 +01:00
|
|
|
$('.page-content').animate({scrollTop: pos + "px"}, 500);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function populate_messages_sent_by_message_type(data) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const layout = {
|
2017-02-07 23:30:34 +01:00
|
|
|
margin: { l: 90, r: 0, b: 0, t: 0 },
|
2017-06-30 21:37:59 +02:00
|
|
|
width: 750,
|
2017-01-11 21:44:59 +01:00
|
|
|
height: 300,
|
2017-02-08 02:24:31 +01:00
|
|
|
font: font_14pt,
|
2017-02-07 23:30:34 +01:00
|
|
|
};
|
|
|
|
|
2017-02-09 03:38:30 +01:00
|
|
|
function make_plot_data(time_series_data, num_steps) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const plot_data = compute_summary_chart_data(
|
2019-10-26 00:21:32 +02:00
|
|
|
time_series_data,
|
|
|
|
num_steps,
|
|
|
|
data.display_order
|
|
|
|
);
|
2019-11-02 00:06:25 +01:00
|
|
|
const labels = [];
|
|
|
|
for (let i = 0; i < plot_data.labels.length; i += 1) {
|
2017-02-09 09:03:25 +01:00
|
|
|
labels.push(plot_data.labels[i] + ' (' + plot_data.percentages[i] + ')');
|
|
|
|
}
|
2017-02-09 03:38:30 +01:00
|
|
|
return {
|
|
|
|
trace: {
|
2017-02-09 09:03:25 +01:00
|
|
|
values: plot_data.values,
|
|
|
|
labels: labels,
|
2017-02-09 03:38:30 +01:00
|
|
|
type: 'pie',
|
|
|
|
direction: 'clockwise',
|
|
|
|
rotation: -90,
|
|
|
|
sort: false,
|
|
|
|
textinfo: "text",
|
2020-07-02 01:45:54 +02:00
|
|
|
text: plot_data.labels.map(() => ''),
|
2017-02-09 09:03:25 +01:00
|
|
|
hoverinfo: "label+value",
|
2017-02-09 03:38:30 +01:00
|
|
|
pull: 0.05,
|
|
|
|
marker: {
|
2017-02-09 09:03:25 +01:00
|
|
|
colors: ['#68537c', '#be6d68', '#b3b348'],
|
2017-02-09 03:38:30 +01:00
|
|
|
},
|
|
|
|
},
|
2017-06-30 21:37:59 +02:00
|
|
|
total_str: "<b>Total messages:</b> " + plot_data.total.toString().
|
2017-02-09 03:38:30 +01:00
|
|
|
replace(/\B(?=(\d{3})+(?!\d))/g, ","),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const plot_data = {
|
2018-05-18 22:13:08 +02:00
|
|
|
everyone: {
|
|
|
|
cumulative: make_plot_data(data.everyone, data.end_times.length),
|
|
|
|
year: make_plot_data(data.everyone, 365),
|
|
|
|
month: make_plot_data(data.everyone, 30),
|
|
|
|
week: make_plot_data(data.everyone, 7),
|
2017-02-07 23:30:34 +01:00
|
|
|
},
|
|
|
|
user: {
|
2017-02-09 03:38:30 +01:00
|
|
|
cumulative: make_plot_data(data.user, data.end_times.length),
|
2017-03-05 11:44:35 +01:00
|
|
|
year: make_plot_data(data.user, 365),
|
|
|
|
month: make_plot_data(data.user, 30),
|
|
|
|
week: make_plot_data(data.user, 7),
|
2017-02-02 01:07:41 +01:00
|
|
|
},
|
2017-01-11 21:44:59 +01:00
|
|
|
};
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
let user_button = 'everyone';
|
|
|
|
let time_button;
|
2017-03-05 11:44:35 +01:00
|
|
|
if (data.end_times.length >= 30) {
|
|
|
|
time_button = 'month';
|
2017-06-30 21:37:59 +02:00
|
|
|
$('#messages_by_type_last_month_button').addClass("selected");
|
2017-03-05 11:44:35 +01:00
|
|
|
} else {
|
|
|
|
time_button = 'cumulative';
|
2017-06-30 21:37:59 +02:00
|
|
|
$('#messages_by_type_cumulative_button').addClass("selected");
|
2017-03-05 11:44:35 +01:00
|
|
|
}
|
2019-11-02 00:06:25 +01:00
|
|
|
const totaldiv = document.getElementById('pie_messages_sent_by_type_total');
|
2017-01-11 21:44:59 +01:00
|
|
|
|
2017-03-05 11:44:35 +01:00
|
|
|
if (data.end_times.length < 365) {
|
2017-06-30 21:37:59 +02:00
|
|
|
$("#pie_messages_sent_by_type button[data-time='year']").remove();
|
2017-03-05 11:44:35 +01:00
|
|
|
if (data.end_times.length < 30) {
|
2017-06-30 21:37:59 +02:00
|
|
|
$("#pie_messages_sent_by_type button[data-time='month']").remove();
|
2017-03-05 11:44:35 +01:00
|
|
|
if (data.end_times.length < 7) {
|
2017-06-30 21:37:59 +02:00
|
|
|
$("#pie_messages_sent_by_type button[data-time='week']").remove();
|
2017-03-05 11:44:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-07 23:30:34 +01:00
|
|
|
function draw_plot() {
|
2017-12-03 12:21:05 +01:00
|
|
|
$('#id_messages_sent_by_message_type > div').removeClass("spinner");
|
2017-02-07 23:30:34 +01:00
|
|
|
Plotly.newPlot('id_messages_sent_by_message_type',
|
2017-02-09 03:38:30 +01:00
|
|
|
[plot_data[user_button][time_button].trace],
|
2017-02-07 23:30:34 +01:00
|
|
|
layout,
|
|
|
|
{displayModeBar: false});
|
2017-02-09 03:38:30 +01:00
|
|
|
totaldiv.innerHTML = plot_data[user_button][time_button].total_str;
|
2017-02-07 23:30:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
draw_plot();
|
|
|
|
|
|
|
|
// Click handlers
|
|
|
|
function set_user_button(button) {
|
2017-06-30 21:37:59 +02:00
|
|
|
$("#pie_messages_sent_by_type button[data-user]").removeClass("selected");
|
|
|
|
button.addClass("selected");
|
2017-02-07 23:30:34 +01:00
|
|
|
}
|
2017-01-11 21:44:59 +01:00
|
|
|
|
2017-02-07 23:30:34 +01:00
|
|
|
function set_time_button(button) {
|
2017-06-30 21:37:59 +02:00
|
|
|
$("#pie_messages_sent_by_type button[data-time]").removeClass("selected");
|
|
|
|
button.addClass("selected");
|
2017-02-07 23:30:34 +01:00
|
|
|
}
|
|
|
|
|
2017-06-30 21:37:59 +02:00
|
|
|
$("#pie_messages_sent_by_type button").click(function () {
|
|
|
|
if ($(this).attr("data-user")) {
|
|
|
|
set_user_button($(this));
|
|
|
|
user_button = $(this).attr("data-user");
|
|
|
|
}
|
|
|
|
if ($(this).attr("data-time")) {
|
|
|
|
set_time_button($(this));
|
|
|
|
time_button = $(this).attr("data-time");
|
|
|
|
}
|
2017-02-07 23:30:34 +01:00
|
|
|
draw_plot();
|
2017-01-11 21:44:59 +01:00
|
|
|
});
|
2016-12-20 02:30:08 +01:00
|
|
|
}
|
|
|
|
|
2017-02-07 23:06:41 +01:00
|
|
|
function populate_number_of_users(data) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const layout = {
|
2017-02-07 23:06:41 +01:00
|
|
|
width: 750,
|
|
|
|
height: 370,
|
2018-06-01 12:58:16 +02:00
|
|
|
margin: { l: 40, r: 0, b: 65, t: 20 },
|
2017-02-07 23:06:41 +01:00
|
|
|
xaxis: {
|
|
|
|
fixedrange: true,
|
2018-06-01 12:58:16 +02:00
|
|
|
rangeslider: { bordercolor: '#D8D8D8', borderwidth: 1 },
|
2017-02-07 23:06:41 +01:00
|
|
|
rangeselector: {
|
2018-06-01 19:33:43 +02:00
|
|
|
x: 0.64, y: -0.79,
|
2017-02-07 23:06:41 +01:00
|
|
|
buttons: [
|
2018-06-01 19:33:43 +02:00
|
|
|
{ count: 2, label: i18n.t('Last 2 months'), step: 'month', stepmode: 'backward' },
|
|
|
|
{ count: 6, label: i18n.t('Last 6 months'), step: 'month', stepmode: 'backward' },
|
2018-05-19 21:00:56 +02:00
|
|
|
{ step: 'all', label: i18n.t('All time') },
|
|
|
|
]}},
|
|
|
|
yaxis: { fixedrange: true, rangemode: 'tozero' },
|
2017-02-08 02:24:31 +01:00
|
|
|
font: font_14pt,
|
2017-02-07 23:06:41 +01:00
|
|
|
};
|
2017-02-08 03:07:05 +01:00
|
|
|
|
2020-07-02 01:45:54 +02:00
|
|
|
const end_dates = data.end_times.map((timestamp) => new Date(timestamp * 1000));
|
2017-02-08 03:07:05 +01:00
|
|
|
|
2020-07-02 01:45:54 +02:00
|
|
|
const text = end_dates.map((date) => format_date(date, false));
|
2017-02-08 03:07:05 +01:00
|
|
|
|
2018-05-19 22:43:02 +02:00
|
|
|
function make_traces(values, type) {
|
|
|
|
return {
|
|
|
|
x: end_dates,
|
|
|
|
y: values,
|
|
|
|
type: type,
|
|
|
|
name: i18n.t("Active users"),
|
|
|
|
hoverinfo: 'none',
|
|
|
|
text: text,
|
|
|
|
visible: true,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-05-26 19:18:53 +02:00
|
|
|
function add_hover_handler() {
|
2020-07-02 01:45:54 +02:00
|
|
|
document.getElementById('id_number_of_users').on('plotly_hover', (data) => {
|
2018-05-26 19:18:53 +02:00
|
|
|
$("#users_hover_info").show();
|
|
|
|
document.getElementById('users_hover_date').innerText =
|
|
|
|
data.points[0].data.text[data.points[0].pointNumber];
|
2019-11-02 00:06:25 +01:00
|
|
|
const values = [null, null, null];
|
2020-07-02 01:45:54 +02:00
|
|
|
data.points.forEach((trace) => {
|
2018-05-26 19:18:53 +02:00
|
|
|
values[trace.curveNumber] = trace.y;
|
|
|
|
});
|
2019-11-02 00:06:25 +01:00
|
|
|
const hover_value_ids = [
|
2018-05-26 19:18:53 +02:00
|
|
|
'users_hover_1day_value', 'users_hover_15day_value', 'users_hover_all_time_value'];
|
2019-11-02 00:06:25 +01:00
|
|
|
for (let i = 0; i < values.length; i += 1) {
|
2018-05-26 19:18:53 +02:00
|
|
|
if (values[i] !== null) {
|
|
|
|
document.getElementById(hover_value_ids[i]).style.display = 'inline';
|
|
|
|
document.getElementById(hover_value_ids[i]).innerText = values[i];
|
|
|
|
} else {
|
|
|
|
document.getElementById(hover_value_ids[i]).style.display = 'none';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const _1day_trace = make_traces(data.everyone._1day, 'bar');
|
|
|
|
const _15day_trace = make_traces(data.everyone._15day, 'scatter');
|
|
|
|
const all_time_trace = make_traces(data.everyone.all_time, 'scatter');
|
2017-02-08 03:07:05 +01:00
|
|
|
|
2017-12-03 12:21:05 +01:00
|
|
|
$('#id_number_of_users > div').removeClass("spinner");
|
|
|
|
|
2018-05-19 22:43:02 +02:00
|
|
|
// Redraw the plot every time for simplicity. If we have perf problems with this in the
|
|
|
|
// future, we can copy the update behavior from populate_messages_sent_over_time
|
|
|
|
function draw_or_update_plot(trace) {
|
|
|
|
$('#1day_actives_button, #15day_actives_button, #all_time_actives_button').removeClass("selected");
|
|
|
|
Plotly.newPlot('id_number_of_users', [trace], layout, {displayModeBar: false});
|
2018-05-26 19:18:53 +02:00
|
|
|
add_hover_handler();
|
2018-05-19 22:43:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$('#1day_actives_button').click(function () {
|
|
|
|
draw_or_update_plot(_1day_trace);
|
|
|
|
$(this).addClass("selected");
|
|
|
|
});
|
|
|
|
|
|
|
|
$('#15day_actives_button').click(function () {
|
|
|
|
draw_or_update_plot(_15day_trace);
|
|
|
|
$(this).addClass("selected");
|
|
|
|
});
|
|
|
|
|
|
|
|
$('#all_time_actives_button').click(function () {
|
|
|
|
draw_or_update_plot(all_time_trace);
|
|
|
|
$(this).addClass("selected");
|
|
|
|
});
|
|
|
|
|
|
|
|
// Initial drawing of plot
|
2019-02-01 08:04:46 +01:00
|
|
|
draw_or_update_plot(all_time_trace, true);
|
2019-01-18 13:54:04 +01:00
|
|
|
$('#all_time_actives_button').addClass("selected");
|
2017-02-07 23:06:41 +01:00
|
|
|
}
|
|
|
|
|
2020-06-11 12:57:43 +02:00
|
|
|
function populate_messages_read_over_time(data) {
|
|
|
|
if (data.end_times.length === 0) {
|
|
|
|
// TODO: do something nicer here
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Helper functions
|
|
|
|
function make_traces(dates, values, type, date_formatter) {
|
2020-07-02 01:45:54 +02:00
|
|
|
const text = dates.map((date) => date_formatter(date));
|
2020-06-11 12:57:43 +02:00
|
|
|
const common = { x: dates, type: type, hoverinfo: 'none', text: text };
|
|
|
|
return {
|
|
|
|
everyone: {
|
|
|
|
name: i18n.t("Everyone"), y: values.everyone, marker: {color: '#5f6ea0'},
|
|
|
|
...common,
|
|
|
|
},
|
|
|
|
me: {
|
|
|
|
name: i18n.t("Me"), y: values.me, marker: {color: '#be6d68'},
|
|
|
|
...common,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
const layout = {
|
|
|
|
barmode: 'group',
|
|
|
|
width: 750,
|
|
|
|
height: 400,
|
|
|
|
margin: { l: 40, r: 0, b: 40, t: 0 },
|
|
|
|
xaxis: {
|
|
|
|
fixedrange: true,
|
|
|
|
rangeslider: { bordercolor: '#D8D8D8', borderwidth: 1 },
|
|
|
|
type: 'date',
|
|
|
|
},
|
|
|
|
yaxis: { fixedrange: true, rangemode: 'tozero' },
|
|
|
|
legend: {
|
|
|
|
x: 0.62, y: 1.12, orientation: 'h', font: font_14pt,
|
|
|
|
},
|
|
|
|
font: font_14pt,
|
|
|
|
};
|
|
|
|
|
|
|
|
function make_rangeselector(x, y, button1, button2) {
|
|
|
|
return {x: x, y: y,
|
|
|
|
buttons: [
|
|
|
|
{stepmode: 'backward', ...button1},
|
|
|
|
{stepmode: 'backward', ...button2},
|
|
|
|
{step: 'all', label: 'All time'}]};
|
|
|
|
}
|
|
|
|
|
|
|
|
// This is also the cumulative rangeselector
|
|
|
|
const daily_rangeselector = make_rangeselector(
|
|
|
|
0.68, -0.62,
|
|
|
|
{count: 10, label: i18n.t('Last 10 days'), step: 'day'},
|
|
|
|
{count: 30, label: i18n.t('Last 30 days'), step: 'day'});
|
|
|
|
const weekly_rangeselector = make_rangeselector(
|
|
|
|
0.656, -0.62,
|
|
|
|
{count: 2, label: i18n.t('Last 2 months'), step: 'month'},
|
|
|
|
{count: 6, label: i18n.t('Last 6 months'), step: 'month'});
|
|
|
|
|
|
|
|
function add_hover_handler() {
|
2020-07-02 01:45:54 +02:00
|
|
|
document.getElementById('id_messages_read_over_time').on('plotly_hover', (data) => {
|
2020-06-11 12:57:43 +02:00
|
|
|
$("#read_hover_info").show();
|
|
|
|
document.getElementById('read_hover_date').innerText =
|
|
|
|
data.points[0].data.text[data.points[0].pointNumber];
|
|
|
|
const values = [null, null];
|
2020-07-02 01:45:54 +02:00
|
|
|
data.points.forEach((trace) => {
|
2020-06-11 12:57:43 +02:00
|
|
|
values[trace.curveNumber] = trace.y;
|
|
|
|
});
|
|
|
|
const read_hover_text_ids = ['read_hover_me', 'read_hover_everyone'];
|
|
|
|
const read_hover_value_ids = ['read_hover_me_value', 'read_hover_everyone_value'];
|
|
|
|
for (let i = 0; i < values.length; i += 1) {
|
|
|
|
if (values[i] !== null) {
|
|
|
|
document.getElementById(read_hover_text_ids[i]).style.display = 'inline';
|
|
|
|
document.getElementById(read_hover_value_ids[i]).style.display = 'inline';
|
|
|
|
document.getElementById(read_hover_value_ids[i]).innerText = values[i];
|
|
|
|
} else {
|
|
|
|
document.getElementById(read_hover_text_ids[i]).style.display = 'none';
|
|
|
|
document.getElementById(read_hover_value_ids[i]).style.display = 'none';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-07-02 01:45:54 +02:00
|
|
|
const start_dates = data.end_times.map((timestamp) =>
|
2020-06-11 12:57:43 +02:00
|
|
|
// data.end_times are the ends of hour long intervals.
|
2020-07-02 01:45:54 +02:00
|
|
|
new Date(timestamp * 1000 - 60 * 60 * 1000)
|
|
|
|
);
|
2020-06-11 12:57:43 +02:00
|
|
|
|
|
|
|
function aggregate_data(aggregation) {
|
|
|
|
let start;
|
|
|
|
let is_boundary;
|
|
|
|
if (aggregation === 'day') {
|
|
|
|
start = floor_to_local_day(start_dates[0]);
|
|
|
|
is_boundary = function (date) {
|
|
|
|
return date.getHours() === 0;
|
|
|
|
};
|
|
|
|
} else if (aggregation === 'week') {
|
|
|
|
start = floor_to_local_week(start_dates[0]);
|
|
|
|
is_boundary = function (date) {
|
|
|
|
return date.getHours() === 0 && date.getDay() === 0;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
const dates = [start];
|
|
|
|
const values = {everyone: [], me: []};
|
|
|
|
let current = {everyone: 0, me: 0};
|
|
|
|
let i_init = 0;
|
|
|
|
if (is_boundary(start_dates[0])) {
|
|
|
|
current = {everyone: data.everyone.read[0],
|
|
|
|
me: data.user.read[0]};
|
|
|
|
i_init = 1;
|
|
|
|
}
|
|
|
|
for (let i = i_init; i < start_dates.length; i += 1) {
|
|
|
|
if (is_boundary(start_dates[i])) {
|
|
|
|
dates.push(start_dates[i]);
|
|
|
|
values.everyone.push(current.everyone);
|
|
|
|
values.me.push(current.me);
|
|
|
|
current = {everyone: 0, me: 0};
|
|
|
|
}
|
|
|
|
current.everyone += data.everyone.read[i];
|
|
|
|
current.me += data.user.read[i];
|
|
|
|
}
|
|
|
|
values.everyone.push(current.everyone);
|
|
|
|
values.me.push(current.me);
|
|
|
|
return {
|
|
|
|
dates: dates, values: values,
|
|
|
|
last_value_is_partial: !is_boundary(new Date(
|
|
|
|
start_dates[start_dates.length - 1].getTime() + 60 * 60 * 1000))};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate traces
|
|
|
|
let date_formatter = function (date) {
|
|
|
|
return format_date(date, true);
|
|
|
|
};
|
|
|
|
let values = {me: data.user.read, everyone: data.everyone.read};
|
|
|
|
|
|
|
|
let info = aggregate_data('day');
|
|
|
|
date_formatter = function (date) {
|
|
|
|
return format_date(date, false);
|
|
|
|
};
|
|
|
|
const last_day_is_partial = info.last_value_is_partial;
|
|
|
|
const daily_traces = make_traces(info.dates, info.values, 'bar', date_formatter);
|
|
|
|
|
|
|
|
info = aggregate_data('week');
|
|
|
|
date_formatter = function (date) {
|
|
|
|
return i18n.t("Week of __date__", {date: format_date(date, false)});
|
|
|
|
};
|
|
|
|
const last_week_is_partial = info.last_value_is_partial;
|
|
|
|
const weekly_traces = make_traces(info.dates, info.values, 'bar', date_formatter);
|
|
|
|
|
2020-07-02 01:45:54 +02:00
|
|
|
const dates = data.end_times.map((timestamp) => new Date(timestamp * 1000));
|
2020-06-11 12:57:43 +02:00
|
|
|
values = {everyone: partial_sums(data.everyone.read),
|
|
|
|
me: partial_sums(data.user.read)};
|
|
|
|
date_formatter = function (date) {
|
|
|
|
return format_date(date, true);
|
|
|
|
};
|
|
|
|
const cumulative_traces = make_traces(dates, values, 'scatter', date_formatter);
|
|
|
|
|
|
|
|
// Functions to draw and interact with the plot
|
|
|
|
|
|
|
|
// We need to redraw plot entirely if switching from (the cumulative) line
|
|
|
|
// graph to any bar graph, since otherwise the rangeselector shows both (plotly bug)
|
|
|
|
let clicked_cumulative = false;
|
|
|
|
|
|
|
|
function draw_or_update_plot(rangeselector, traces, last_value_is_partial, initial_draw) {
|
|
|
|
$('#read_daily_button, #read_weekly_button, #read_cumulative_button').removeClass("selected");
|
|
|
|
$('#id_messages_read_over_time > div').removeClass("spinner");
|
|
|
|
if (initial_draw) {
|
|
|
|
traces.everyone.visible = true;
|
|
|
|
traces.me.visible = 'legendonly';
|
|
|
|
} else {
|
|
|
|
const plotDiv = document.getElementById('id_messages_read_over_time');
|
|
|
|
traces.me.visible = plotDiv.data[0].visible;
|
|
|
|
traces.everyone.visible = plotDiv.data[1].visible;
|
|
|
|
}
|
|
|
|
layout.xaxis.rangeselector = rangeselector;
|
|
|
|
if (clicked_cumulative || initial_draw) {
|
|
|
|
Plotly.newPlot('id_messages_read_over_time',
|
|
|
|
[traces.me, traces.everyone], layout, {displayModeBar: false});
|
|
|
|
add_hover_handler();
|
|
|
|
} else {
|
|
|
|
Plotly.deleteTraces('id_messages_read_over_time', [0, 1]);
|
|
|
|
Plotly.addTraces('id_messages_read_over_time', [traces.me, traces.everyone]);
|
|
|
|
Plotly.relayout('id_messages_read_over_time', layout);
|
|
|
|
}
|
|
|
|
$('#id_messages_read_over_time').attr('last_value_is_partial', last_value_is_partial);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Click handlers for aggregation buttons
|
|
|
|
$('#read_daily_button').click(function () {
|
|
|
|
draw_or_update_plot(daily_rangeselector, daily_traces, last_day_is_partial, false);
|
|
|
|
$(this).addClass("selected");
|
|
|
|
clicked_cumulative = false;
|
|
|
|
});
|
|
|
|
|
|
|
|
$('#read_weekly_button').click(function () {
|
|
|
|
draw_or_update_plot(weekly_rangeselector, weekly_traces, last_week_is_partial, false);
|
|
|
|
$(this).addClass("selected");
|
|
|
|
clicked_cumulative = false;
|
|
|
|
});
|
|
|
|
|
|
|
|
$('#read_cumulative_button').click(function () {
|
|
|
|
clicked_cumulative = false;
|
|
|
|
draw_or_update_plot(daily_rangeselector, cumulative_traces, false, false);
|
|
|
|
$(this).addClass("selected");
|
|
|
|
clicked_cumulative = true;
|
|
|
|
});
|
|
|
|
|
|
|
|
// Initial drawing of plot
|
|
|
|
if (weekly_traces.everyone.x.length < 12) {
|
|
|
|
draw_or_update_plot(daily_rangeselector, daily_traces, last_day_is_partial, true);
|
|
|
|
$('#read_daily_button').addClass("selected");
|
|
|
|
} else {
|
|
|
|
draw_or_update_plot(weekly_rangeselector, weekly_traces, last_week_is_partial, true);
|
|
|
|
$('#read_weekly_button').addClass("selected");
|
|
|
|
}
|
|
|
|
}
|
2018-05-17 22:25:31 +02:00
|
|
|
|
|
|
|
function get_chart_data(data, callback) {
|
|
|
|
$.get({
|
2018-05-18 01:04:44 +02:00
|
|
|
url: '/json/analytics/chart_data' + page_params.data_url_suffix,
|
2018-05-17 22:25:31 +02:00
|
|
|
data: data,
|
|
|
|
idempotent: true,
|
|
|
|
success: function (data) {
|
|
|
|
callback(data);
|
|
|
|
update_last_full_update(data.end_times);
|
|
|
|
},
|
|
|
|
error: function (xhr) {
|
|
|
|
$('#id_stats_errors').show().text(JSON.parse(xhr.responseText).msg);
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
get_chart_data(
|
|
|
|
{chart_name: 'messages_sent_over_time', min_length: '10'},
|
|
|
|
populate_messages_sent_over_time
|
|
|
|
);
|
|
|
|
|
|
|
|
get_chart_data(
|
|
|
|
{chart_name: 'messages_sent_by_client', min_length: '10'},
|
|
|
|
populate_messages_sent_by_client
|
|
|
|
);
|
|
|
|
|
|
|
|
get_chart_data(
|
|
|
|
{chart_name: 'messages_sent_by_message_type', min_length: '10'},
|
|
|
|
populate_messages_sent_by_message_type
|
|
|
|
);
|
|
|
|
|
2017-11-30 04:40:26 +01:00
|
|
|
get_chart_data(
|
|
|
|
{chart_name: 'number_of_humans', min_length: '10'},
|
|
|
|
populate_number_of_users
|
|
|
|
);
|
2020-06-11 12:57:43 +02:00
|
|
|
|
|
|
|
get_chart_data(
|
|
|
|
{chart_name: 'messages_read_over_time', min_length: '10'},
|
|
|
|
populate_messages_read_over_time
|
|
|
|
);
|