mirror of
https://github.com/discourse/discourse.git
synced 2025-04-19 21:29:05 +08:00
DEV: Add support for radar charts when displaying reports. (#24274)
Reports can have the radar type, which will get rendered by the `admin-report-radar` component.
This commit is contained in:
parent
fe0b0edf57
commit
3c29a84d98
@ -0,0 +1,3 @@
|
||||
<div class="chart-canvas-container">
|
||||
<canvas class="chart-canvas"></canvas>
|
||||
</div>
|
@ -0,0 +1,137 @@
|
||||
import Component from "@ember/component";
|
||||
import { schedule } from "@ember/runloop";
|
||||
import { classNames } from "@ember-decorators/component";
|
||||
import loadScript from "discourse/lib/load-script";
|
||||
import discourseDebounce from "discourse-common/lib/debounce";
|
||||
import { makeArray } from "discourse-common/lib/helpers";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
import Report from "admin/models/report";
|
||||
|
||||
@classNames("admin-report-chart", "admin-report-radar")
|
||||
export default class AdminReportRadar extends Component {
|
||||
didInsertElement() {
|
||||
super.didInsertElement(...arguments);
|
||||
|
||||
window.addEventListener("resize", this._resizeHandler);
|
||||
}
|
||||
|
||||
willDestroyElement() {
|
||||
super.willDestroyElement(...arguments);
|
||||
|
||||
window.removeEventListener("resize", this._resizeHandler);
|
||||
this._resetChart();
|
||||
}
|
||||
|
||||
didReceiveAttrs() {
|
||||
super.didReceiveAttrs(...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,
|
||||
data: cd.data.mapBy("y"),
|
||||
fill: true,
|
||||
backgroundColor: this._hexToRGBA(cd.color, 0.3),
|
||||
borderColor: cd.color,
|
||||
pointBackgroundColor: cd.color,
|
||||
pointBorderColor: "#fff",
|
||||
pointHoverBackgroundColor: "#fff",
|
||||
pointHoverBorderColor: cd.color,
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
loadScript("/javascripts/Chart.min.js").then(() => {
|
||||
this._resetChart();
|
||||
|
||||
this._chart = new window.Chart(context, this._buildChartConfig(data));
|
||||
});
|
||||
}
|
||||
|
||||
_buildChartConfig(data) {
|
||||
return {
|
||||
type: "radar",
|
||||
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.r || 0, 10))
|
||||
);
|
||||
return `= ${total}`;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
_resetChart() {
|
||||
this._chart?.destroy();
|
||||
this._chart = null;
|
||||
}
|
||||
|
||||
_hexToRGBA(hexCode, opacity) {
|
||||
let hex = hexCode.replace("#", "");
|
||||
|
||||
if (hex.length === 3) {
|
||||
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
||||
}
|
||||
|
||||
const r = parseInt(hex.substring(0, 2), 16),
|
||||
g = parseInt(hex.substring(2, 4), 16),
|
||||
b = parseInt(hex.substring(4, 6), 16);
|
||||
|
||||
return `rgba(${r},${g},${b}, ${opacity})`;
|
||||
}
|
||||
}
|
@ -154,7 +154,7 @@ export default class Report extends EmberObject {
|
||||
),
|
||||
};
|
||||
});
|
||||
} else {
|
||||
} else if (report.modes[0] !== "radar") {
|
||||
report[filledField] = fillMissingDates(
|
||||
JSON.parse(JSON.stringify(report[dataField])),
|
||||
startDateFormatted,
|
||||
|
@ -1046,6 +1046,7 @@ a.inline-editable-field {
|
||||
@import "common/admin/admin_report";
|
||||
@import "common/admin/admin_report_counters";
|
||||
@import "common/admin/admin_report_chart";
|
||||
@import "common/admin/admin_report_radar";
|
||||
@import "common/admin/admin_report_stacked_chart";
|
||||
@import "common/admin/admin_report_table";
|
||||
@import "common/admin/admin_report_inline_table";
|
||||
|
@ -0,0 +1,5 @@
|
||||
.admin-report-radar {
|
||||
.chart-canvas-container {
|
||||
height: 250px;
|
||||
}
|
||||
}
|
@ -104,7 +104,7 @@ class Report
|
||||
@average = false
|
||||
@percent = false
|
||||
@higher_is_better = true
|
||||
@modes = %i[table chart]
|
||||
@modes = %i[table chart radar]
|
||||
@prev_data = nil
|
||||
@dates_filtering = true
|
||||
@available_filters = {}
|
||||
|
Loading…
x
Reference in New Issue
Block a user