2020-12-11 03:48:25 +08:00
|
|
|
import { debounce, schedule } from "@ember/runloop";
|
2019-10-24 00:30:52 +08:00
|
|
|
import Component from "@ember/component";
|
2018-07-20 02:33:11 +08:00
|
|
|
import loadScript from "discourse/lib/load-script";
|
2019-11-02 01:06:50 +08:00
|
|
|
import { makeArray } from "discourse-common/lib/helpers";
|
2018-07-20 02:33:11 +08:00
|
|
|
import { number } from "discourse/lib/formatter";
|
|
|
|
|
2019-10-24 00:30:52 +08:00
|
|
|
export default Component.extend({
|
2018-07-20 02:33:11 +08:00
|
|
|
classNames: ["admin-report-chart"],
|
|
|
|
limit: 8,
|
|
|
|
total: 0,
|
2020-07-28 22:14:41 +08:00
|
|
|
options: null,
|
2018-07-20 02:33:11 +08:00
|
|
|
|
2018-12-12 18:11:56 +08:00
|
|
|
init() {
|
|
|
|
this._super(...arguments);
|
|
|
|
|
|
|
|
this.resizeHandler = () =>
|
2020-12-11 03:48:25 +08:00
|
|
|
debounce(this, this._scheduleChartRendering, 500);
|
2018-12-12 18:11:56 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
didInsertElement() {
|
|
|
|
this._super(...arguments);
|
|
|
|
|
|
|
|
$(window).on("resize.chart", this.resizeHandler);
|
|
|
|
},
|
|
|
|
|
2018-07-20 02:33:11 +08:00
|
|
|
willDestroyElement() {
|
|
|
|
this._super(...arguments);
|
|
|
|
|
2018-12-12 18:11:56 +08:00
|
|
|
$(window).off("resize.chart", this.resizeHandler);
|
|
|
|
|
2018-07-20 02:33:11 +08:00
|
|
|
this._resetChart();
|
|
|
|
},
|
|
|
|
|
|
|
|
didReceiveAttrs() {
|
|
|
|
this._super(...arguments);
|
|
|
|
|
2020-12-11 03:48:25 +08:00
|
|
|
debounce(this, this._scheduleChartRendering, 100);
|
2018-12-12 18:11:56 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
_scheduleChartRendering() {
|
2019-10-30 21:48:24 +08:00
|
|
|
schedule("afterRender", () => {
|
2019-07-16 18:45:15 +08:00
|
|
|
this._renderChart(
|
|
|
|
this.model,
|
|
|
|
this.element && this.element.querySelector(".chart-canvas")
|
|
|
|
);
|
2018-12-12 18:11:56 +08:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2019-07-16 18:45:15 +08:00
|
|
|
_renderChart(model, chartCanvas) {
|
2020-09-22 22:28:28 +08:00
|
|
|
if (!chartCanvas) {
|
|
|
|
return;
|
|
|
|
}
|
2018-12-12 18:11:56 +08:00
|
|
|
|
2019-07-16 18:45:15 +08:00
|
|
|
const context = chartCanvas.getContext("2d");
|
2020-07-28 22:14:41 +08:00
|
|
|
const chartData = this._applyChartGrouping(
|
|
|
|
model,
|
|
|
|
makeArray(model.get("chartData") || model.get("data"), "weekly"),
|
|
|
|
this.options
|
|
|
|
);
|
2019-11-01 01:55:01 +08:00
|
|
|
const prevChartData = makeArray(
|
2018-12-12 18:11:56 +08:00
|
|
|
model.get("prevChartData") || model.get("prev_data")
|
|
|
|
);
|
|
|
|
|
|
|
|
const labels = chartData.map((d) => d.x);
|
|
|
|
|
|
|
|
const data = {
|
|
|
|
labels,
|
|
|
|
datasets: [
|
|
|
|
{
|
|
|
|
data: chartData.map((d) => Math.round(parseFloat(d.y))),
|
|
|
|
backgroundColor: prevChartData.length
|
|
|
|
? "transparent"
|
|
|
|
: model.secondary_color,
|
2019-01-22 17:22:41 +08:00
|
|
|
borderColor: model.primary_color,
|
|
|
|
pointRadius: 3,
|
|
|
|
borderWidth: 1,
|
|
|
|
pointBackgroundColor: model.primary_color,
|
|
|
|
pointBorderColor: model.primary_color,
|
2018-12-12 18:11:56 +08:00
|
|
|
},
|
|
|
|
],
|
|
|
|
};
|
|
|
|
|
|
|
|
if (prevChartData.length) {
|
|
|
|
data.datasets.push({
|
|
|
|
data: prevChartData.map((d) => Math.round(parseFloat(d.y))),
|
|
|
|
borderColor: model.primary_color,
|
|
|
|
borderDash: [5, 5],
|
|
|
|
backgroundColor: "transparent",
|
|
|
|
borderWidth: 1,
|
|
|
|
pointRadius: 0,
|
2018-07-20 02:33:11 +08:00
|
|
|
});
|
2018-12-12 18:11:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
loadScript("/javascripts/Chart.min.js").then(() => {
|
|
|
|
this._resetChart();
|
2019-12-11 22:49:17 +08:00
|
|
|
|
|
|
|
if (!this.element) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-07-28 22:14:41 +08:00
|
|
|
this._chart = new window.Chart(
|
|
|
|
context,
|
|
|
|
this._buildChartConfig(data, this.options)
|
|
|
|
);
|
2018-07-20 02:33:11 +08:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2020-07-28 22:14:41 +08:00
|
|
|
_buildChartConfig(data, options) {
|
2018-07-20 02:33:11 +08:00
|
|
|
return {
|
|
|
|
type: "line",
|
|
|
|
data,
|
|
|
|
options: {
|
|
|
|
tooltips: {
|
|
|
|
callbacks: {
|
|
|
|
title: (tooltipItem) =>
|
|
|
|
moment(tooltipItem[0].xLabel, "YYYY-MM-DD").format("LL"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
legend: {
|
|
|
|
display: false,
|
|
|
|
},
|
|
|
|
responsive: true,
|
|
|
|
maintainAspectRatio: false,
|
2019-12-11 22:49:17 +08:00
|
|
|
responsiveAnimationDuration: 0,
|
|
|
|
animation: {
|
|
|
|
duration: 0,
|
|
|
|
},
|
2018-07-20 02:33:11 +08:00
|
|
|
layout: {
|
|
|
|
padding: {
|
|
|
|
left: 0,
|
|
|
|
top: 0,
|
|
|
|
right: 0,
|
|
|
|
bottom: 0,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
scales: {
|
|
|
|
yAxes: [
|
|
|
|
{
|
|
|
|
display: true,
|
2018-10-30 16:58:03 +08:00
|
|
|
ticks: {
|
|
|
|
userCallback: (label) => {
|
2020-09-22 22:28:28 +08:00
|
|
|
if (Math.floor(label) === label) {
|
|
|
|
return label;
|
|
|
|
}
|
2018-10-30 16:58:03 +08:00
|
|
|
},
|
2019-12-11 22:49:17 +08:00
|
|
|
callback: (label) => number(label),
|
|
|
|
sampleSize: 5,
|
|
|
|
maxRotation: 25,
|
|
|
|
minRotation: 25,
|
2018-10-30 16:58:03 +08:00
|
|
|
},
|
2018-07-20 02:33:11 +08:00
|
|
|
},
|
|
|
|
],
|
|
|
|
xAxes: [
|
|
|
|
{
|
|
|
|
display: true,
|
|
|
|
gridLines: { display: false },
|
|
|
|
type: "time",
|
|
|
|
time: {
|
2020-07-28 22:14:41 +08:00
|
|
|
unit: this._unitForGrouping(options),
|
2019-12-11 22:49:17 +08:00
|
|
|
},
|
|
|
|
ticks: {
|
|
|
|
sampleSize: 5,
|
|
|
|
maxRotation: 50,
|
|
|
|
minRotation: 50,
|
2018-07-20 02:33:11 +08:00
|
|
|
},
|
|
|
|
},
|
|
|
|
],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
_resetChart() {
|
|
|
|
if (this._chart) {
|
|
|
|
this._chart.destroy();
|
|
|
|
this._chart = null;
|
|
|
|
}
|
2020-07-28 22:14:41 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
_applyChartGrouping(model, data, options) {
|
|
|
|
if (!options.chartGrouping || options.chartGrouping === "daily") {
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
options.chartGrouping === "weekly" ||
|
|
|
|
options.chartGrouping === "monthly"
|
|
|
|
) {
|
|
|
|
const isoKind = options.chartGrouping === "weekly" ? "isoWeek" : "month";
|
|
|
|
const kind = options.chartGrouping === "weekly" ? "week" : "month";
|
|
|
|
const startMoment = moment(model.start_date, "YYYY-MM-DD");
|
|
|
|
|
|
|
|
let currentIndex = 0;
|
|
|
|
let currentStart = startMoment.clone().startOf(isoKind);
|
|
|
|
let currentEnd = startMoment.clone().endOf(isoKind);
|
|
|
|
const transformedData = [
|
|
|
|
{
|
|
|
|
x: currentStart.format("YYYY-MM-DD"),
|
|
|
|
y: 0,
|
|
|
|
},
|
|
|
|
];
|
|
|
|
|
|
|
|
data.forEach((d) => {
|
|
|
|
let date = moment(d.x, "YYYY-MM-DD");
|
|
|
|
|
|
|
|
if (!date.isBetween(currentStart, currentEnd)) {
|
|
|
|
currentIndex += 1;
|
|
|
|
currentStart = currentStart.add(1, kind).startOf(isoKind);
|
|
|
|
currentEnd = currentEnd.add(1, kind).endOf(isoKind);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (transformedData[currentIndex]) {
|
|
|
|
transformedData[currentIndex].y += d.y;
|
|
|
|
} else {
|
|
|
|
transformedData[currentIndex] = {
|
|
|
|
x: d.x,
|
|
|
|
y: d.y,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return transformedData;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ensure we return something if grouping is unknown
|
|
|
|
return data;
|
|
|
|
},
|
|
|
|
|
|
|
|
_unitForGrouping(options) {
|
|
|
|
switch (options.chartGrouping) {
|
|
|
|
case "monthly":
|
|
|
|
return "month";
|
|
|
|
case "weekly":
|
|
|
|
return "week";
|
|
|
|
default:
|
|
|
|
return "day";
|
|
|
|
}
|
2018-07-20 02:33:11 +08:00
|
|
|
},
|
|
|
|
});
|