mirror of
https://github.com/discourse/discourse.git
synced 2024-12-03 10:13:38 +08:00
3e23bd4745
Before this change, we were using the labels from the original chartData to the chart builder, and we would then apply our collapse function on each dataset which could change the labels and cause a mismatch. This was very visible when using quarterly periods on consolidated pageviews.
160 lines
3.7 KiB
JavaScript
160 lines
3.7 KiB
JavaScript
import Report from "admin/models/report";
|
|
import Component from "@ember/component";
|
|
import discourseDebounce from "discourse-common/lib/debounce";
|
|
import loadScript from "discourse/lib/load-script";
|
|
import { makeArray } from "discourse-common/lib/helpers";
|
|
import { number } from "discourse/lib/formatter";
|
|
import { schedule } from "@ember/runloop";
|
|
import { bind } from "discourse-common/utils/decorators";
|
|
|
|
export default Component.extend({
|
|
classNames: ["admin-report-chart", "admin-report-stacked-chart"],
|
|
|
|
didInsertElement() {
|
|
this._super(...arguments);
|
|
|
|
window.addEventListener("resize", this._resizeHandler);
|
|
},
|
|
|
|
willDestroyElement() {
|
|
this._super(...arguments);
|
|
|
|
window.removeEventListener("resize", this._resizeHandler);
|
|
this._resetChart();
|
|
},
|
|
|
|
didReceiveAttrs() {
|
|
this._super(...arguments);
|
|
|
|
discourseDebounce(this, this._scheduleChartRendering, 100);
|
|
},
|
|
|
|
@bind
|
|
_resizeHandler() {
|
|
discourseDebounce(this, this._scheduleChartRendering, 500);
|
|
},
|
|
|
|
_scheduleChartRendering() {
|
|
schedule("afterRender", () => {
|
|
if (!this.element) {
|
|
return;
|
|
}
|
|
|
|
this._renderChart(
|
|
this.model,
|
|
this.element.querySelector(".chart-canvas")
|
|
);
|
|
});
|
|
},
|
|
|
|
_renderChart(model, chartCanvas) {
|
|
if (!chartCanvas) {
|
|
return;
|
|
}
|
|
|
|
const context = chartCanvas.getContext("2d");
|
|
|
|
const chartData = makeArray(model.chartData || model.data).map((cd) => {
|
|
return {
|
|
label: cd.label,
|
|
color: cd.color,
|
|
data: Report.collapse(model, cd.data),
|
|
};
|
|
});
|
|
|
|
const data = {
|
|
labels: chartData[0].data.mapBy("x"),
|
|
datasets: chartData.map((cd) => {
|
|
return {
|
|
label: cd.label,
|
|
stack: "pageviews-stack",
|
|
data: cd.data,
|
|
backgroundColor: cd.color,
|
|
};
|
|
}),
|
|
};
|
|
|
|
loadScript("/javascripts/Chart.min.js").then(() => {
|
|
this._resetChart();
|
|
|
|
this._chart = new window.Chart(context, this._buildChartConfig(data));
|
|
});
|
|
},
|
|
|
|
_buildChartConfig(data) {
|
|
return {
|
|
type: "bar",
|
|
data,
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
responsiveAnimationDuration: 0,
|
|
hover: { mode: "index" },
|
|
animation: {
|
|
duration: 0,
|
|
},
|
|
plugins: {
|
|
tooltip: {
|
|
mode: "index",
|
|
intersect: false,
|
|
callbacks: {
|
|
beforeFooter: (tooltipItem) => {
|
|
let total = 0;
|
|
tooltipItem.forEach(
|
|
(item) => (total += parseInt(item.parsed.y || 0, 10))
|
|
);
|
|
return `= ${total}`;
|
|
},
|
|
title: (tooltipItem) =>
|
|
moment(tooltipItem[0].label, "YYYY-MM-DD").format("LL"),
|
|
},
|
|
},
|
|
},
|
|
|
|
layout: {
|
|
padding: {
|
|
left: 0,
|
|
top: 0,
|
|
right: 0,
|
|
bottom: 0,
|
|
},
|
|
},
|
|
scales: {
|
|
y: [
|
|
{
|
|
stacked: true,
|
|
display: true,
|
|
ticks: {
|
|
callback: (label) => number(label),
|
|
sampleSize: 5,
|
|
maxRotation: 25,
|
|
minRotation: 25,
|
|
},
|
|
},
|
|
],
|
|
x: [
|
|
{
|
|
display: true,
|
|
gridLines: { display: false },
|
|
type: "time",
|
|
time: {
|
|
unit: Report.unitForDatapoints(data.labels.length),
|
|
},
|
|
ticks: {
|
|
sampleSize: 5,
|
|
maxRotation: 50,
|
|
minRotation: 50,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
};
|
|
},
|
|
|
|
_resetChart() {
|
|
this._chart?.destroy();
|
|
this._chart = null;
|
|
},
|
|
});
|