FEATURE: Pie chart option for poll results (#8352)

This commit is contained in:
Mark VanLandingham
2019-11-25 11:51:01 -06:00
committed by GitHub
parent 720101b3ee
commit b92a8131c0
23 changed files with 947 additions and 47 deletions

View File

@ -8,6 +8,11 @@ import evenRound from "discourse/plugins/poll/lib/even-round";
import { avatarFor } from "discourse/widgets/post";
import round from "discourse/lib/round";
import { relativeAge } from "discourse/lib/formatter";
import loadScript from "discourse/lib/load-script";
import { getColors } from "../lib/chart-colors";
import { later } from "@ember/runloop";
import { classify } from "@ember/string";
import { PIE_CHART_TYPE } from "../controllers/poll-ui-builder";
function optionHtml(option) {
const $node = $(`<span>${option.html}</span>`);
@ -323,7 +328,11 @@ createWidget("discourse-poll-container", {
if (attrs.showResults) {
const type = poll.get("type") === "number" ? "number" : "standard";
return this.attach(`discourse-poll-${type}-results`, attrs);
const resultsWidget =
type === "number" || attrs.poll.chart_type !== PIE_CHART_TYPE
? `discourse-poll-${type}-results`
: "discourse-poll-pie-chart";
return this.attach(resultsWidget, attrs);
} else if (options) {
return h(
"ul",
@ -415,6 +424,196 @@ createWidget("discourse-poll-info", {
}
});
function transformUserFieldToLabel(fieldName) {
let transformed = fieldName.split("_").filter(Boolean);
transformed[0] = classify(transformed[0]);
return transformed.join(" ");
}
createWidget("discourse-poll-grouped-pies", {
tagName: "div.poll-grouped-pies",
buildAttributes(attrs) {
return {
id: `poll-results-grouped-pie-charts-${attrs.id}`
};
},
html(attrs) {
const fields = Object.assign({}, attrs.groupableUserFields);
const fieldSelectId = `field-select-${attrs.id}`;
attrs.groupedBy = attrs.groupedBy || fields[0];
let contents = [];
const btn = this.attach("button", {
className: "btn-default poll-group-by-toggle",
label: "poll.ungroup-results.label",
title: "poll.ungroup-results.title",
icon: "far-eye-slash",
action: "toggleGroupedPieCharts"
});
const select = h(
`select#${fieldSelectId}.poll-group-by-selector`,
{ value: attrs.groupBy },
attrs.groupableUserFields.map(field => {
return h("option", { value: field }, transformUserFieldToLabel(field));
})
);
contents.push(h("div.poll-grouped-pies-controls", [btn, select]));
ajax("/polls/grouped_poll_results.json", {
data: {
post_id: attrs.post.id,
poll_name: attrs.poll.name,
user_field_name: attrs.groupedBy
}
})
.catch(error => {
if (error) {
popupAjaxError(error);
} else {
bootbox.alert(I18n.t("poll.error_while_fetching_voters"));
}
})
.then(result => {
let groupBySelect = document.getElementById(fieldSelectId);
if (!groupBySelect) return;
groupBySelect.value = attrs.groupedBy;
const parent = document.getElementById(
`poll-results-grouped-pie-charts-${attrs.id}`
);
for (
let chartIdx = 0;
chartIdx < result.grouped_results.length;
chartIdx++
) {
const data = result.grouped_results[chartIdx].options.mapBy("votes");
const labels = result.grouped_results[chartIdx].options.mapBy("html");
const chartConfig = pieChartConfig(data, labels, 1.2);
const canvasId = `pie-${attrs.id}-${chartIdx}`;
let el = document.querySelector(`#${canvasId}`);
if (!el) {
const container = document.createElement("div");
container.classList.add("poll-grouped-pie-container");
const label = document.createElement("label");
label.classList.add("poll-pie-label");
label.textContent = result.grouped_results[chartIdx].group;
const canvas = document.createElement("canvas");
canvas.classList.add(`poll-grouped-pie-${attrs.id}`);
canvas.id = canvasId;
container.appendChild(label);
container.appendChild(canvas);
parent.appendChild(container);
// eslint-disable-next-line
new Chart(canvas.getContext("2d"), chartConfig);
} else {
// eslint-disable-next-line
Chart.helpers.each(Chart.instances, function(instance) {
if (instance.chart.canvas.id === canvasId && el.$chartjs) {
instance.destroy();
// eslint-disable-next-line
new Chart(el.getContext("2d"), chartConfig);
}
});
}
}
});
return contents;
},
click(e) {
let select = $(e.target).closest("select");
if (select.length) {
this.sendWidgetAction("refreshCharts", select[0].value);
}
}
});
function clearPieChart(id) {
let el = document.querySelector(`#poll-results-chart-${id}`);
el && el.parentNode.removeChild(el);
}
createWidget("discourse-poll-pie-canvas", {
tagName: "canvas.poll-results-canvas",
buildAttributes(attrs) {
return {
id: `poll-results-chart-${attrs.id}`
};
}
});
createWidget("discourse-poll-pie-chart", {
tagName: "div.poll-results-chart",
html(attrs) {
const contents = [];
if (!attrs.showResults) {
clearPieChart(this.attrs.id);
return contents;
}
let btn;
let chart;
if (attrs.groupResults && attrs.groupableUserFields.length > 0) {
chart = this.attach("discourse-poll-grouped-pies", attrs);
clearPieChart(this.attrs.id);
} else {
if (attrs.groupableUserFields.length) {
btn = this.attach("button", {
className: "btn-default poll-group-by-toggle",
label: "poll.group-results.label",
title: "poll.group-results.title",
icon: "far-eye",
action: "toggleGroupedPieCharts"
});
}
const data = attrs.poll.options.mapBy("votes");
const labels = attrs.poll.options.mapBy("html");
loadScript("/javascripts/Chart.min.js").then(() => {
later(() => {
const el = document.querySelector(
`#poll-results-chart-${this.attrs.id}`
);
const config = pieChartConfig(data, labels);
// eslint-disable-next-line
new Chart(el.getContext("2d"), config);
});
});
chart = this.attach("discourse-poll-pie-canvas", attrs);
}
contents.push(btn);
contents.push(chart);
return contents;
}
});
function pieChartConfig(data, labels, aspectRatio = 2.0) {
return {
type: PIE_CHART_TYPE,
data: {
datasets: [
{
data,
backgroundColor: getColors(data.length)
}
],
labels
},
options: {
responsive: true,
aspectRatio,
animation: { duration: 400 }
}
};
}
createWidget("discourse-poll-buttons", {
tagName: "div.poll-buttons",
@ -433,7 +632,7 @@ createWidget("discourse-poll-buttons", {
const castVotesDisabled = !attrs.canCastVotes;
contents.push(
this.attach("button", {
className: `btn cast-votes ${
className: `cast-votes ${
castVotesDisabled ? "btn-default" : "btn-primary"
}`,
label: "poll.cast-votes.label",
@ -448,7 +647,7 @@ createWidget("discourse-poll-buttons", {
if (attrs.showResults || hideResultsDisabled) {
contents.push(
this.attach("button", {
className: "btn btn-default toggle-results",
className: "btn-default toggle-results",
label: "poll.hide-results.label",
title: "poll.hide-results.title",
icon: "far-eye-slash",
@ -466,7 +665,7 @@ createWidget("discourse-poll-buttons", {
} else {
contents.push(
this.attach("button", {
className: "btn btn-default toggle-results",
className: "btn-default toggle-results",
label: "poll.show-results.label",
title: "poll.show-results.title",
icon: "far-eye",
@ -521,7 +720,7 @@ createWidget("discourse-poll-buttons", {
if (!attrs.isAutomaticallyClosed) {
contents.push(
this.attach("button", {
className: "btn btn-default toggle-status",
className: "btn-default toggle-status",
label: "poll.open.label",
title: "poll.open.title",
icon: "unlock-alt",
@ -532,7 +731,7 @@ createWidget("discourse-poll-buttons", {
} else {
contents.push(
this.attach("button", {
className: "btn toggle-status btn-danger",
className: "toggle-status btn-danger",
label: "poll.close.label",
title: "poll.close.title",
icon: "lock",
@ -547,11 +746,14 @@ createWidget("discourse-poll-buttons", {
});
export default createWidget("discourse-poll", {
tagName: "div.poll",
tagName: "div",
buildKey: attrs => `poll-${attrs.id}`,
buildAttributes(attrs) {
let cssClasses = "poll";
if (attrs.poll.chart_type === PIE_CHART_TYPE) cssClasses += " pie";
return {
class: cssClasses,
"data-poll-name": attrs.poll.get("name"),
"data-poll-type": attrs.poll.get("type")
};
@ -808,5 +1010,21 @@ export default createWidget("discourse-poll", {
.finally(() => {
state.loading = false;
});
},
toggleGroupedPieCharts() {
this.attrs.groupResults = !this.attrs.groupResults;
},
refreshCharts(newGroupedByValue) {
let el = document.getElementById(
`poll-results-grouped-pie-charts-${this.attrs.id}`
);
Array.from(el.getElementsByClassName("poll-grouped-pie-container")).forEach(
container => {
el.removeChild(container);
}
);
this.attrs.groupedBy = newGroupedByValue;
}
});