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"; export default Component.extend({ classNames: ["admin-report-chart"], limit: 8, total: 0, options: null, init() { this._super(...arguments); this.resizeHandler = () => discourseDebounce(this, this._scheduleChartRendering, 500); }, didInsertElement() { this._super(...arguments); $(window).on("resize.chart", this.resizeHandler); }, willDestroyElement() { this._super(...arguments); $(window).off("resize.chart", this.resizeHandler); this._resetChart(); }, didReceiveAttrs() { this._super(...arguments); discourseDebounce(this, this._scheduleChartRendering, 100); }, _scheduleChartRendering() { schedule("afterRender", () => { this._renderChart( this.model, this.element && this.element.querySelector(".chart-canvas") ); }); }, _renderChart(model, chartCanvas) { if (!chartCanvas) { return; } const context = chartCanvas.getContext("2d"); const chartData = this._applyChartGrouping( model, makeArray(model.get("chartData") || model.get("data"), "weekly"), this.options ); const prevChartData = makeArray( 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, borderColor: model.primary_color, pointRadius: 3, borderWidth: 1, pointBackgroundColor: model.primary_color, pointBorderColor: model.primary_color, }, ], }; 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, }); } loadScript("/javascripts/Chart.min.js").then(() => { this._resetChart(); if (!this.element) { return; } this._chart = new window.Chart( context, this._buildChartConfig(data, this.options) ); }); }, _buildChartConfig(data, options) { 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, responsiveAnimationDuration: 0, animation: { duration: 0, }, layout: { padding: { left: 0, top: 0, right: 0, bottom: 0, }, }, scales: { yAxes: [ { display: true, ticks: { userCallback: (label) => { if (Math.floor(label) === label) { return label; } }, callback: (label) => number(label), sampleSize: 5, maxRotation: 25, minRotation: 25, }, }, ], xAxes: [ { display: true, gridLines: { display: false }, type: "time", time: { unit: this._unitForGrouping(options), }, ticks: { sampleSize: 5, maxRotation: 50, minRotation: 50, }, }, ], }, }, }; }, _resetChart() { if (this._chart) { this._chart.destroy(); this._chart = null; } }, _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"; } }, });