wizechart
Version:
A jQuery + Bootstrap simple progress bar chart plugin
145 lines (128 loc) • 4.5 kB
JavaScript
import $ from "jquery";
(function ($) {
$.fn.WizeChart = function (options) {
const defaultOptions = {
data: null,
ajax: null,
label: "{label}",
value: (val) => val,
popover: {
title: "{label}",
content: (val) => `${val} doses`,
placement: "top",
},
color: "primary",
colors: null,
rulerPoints: 5,
};
const settings = $.extend(true, {}, defaultOptions, options);
const formatText = (tpl, val) =>
typeof tpl === "function" ? tpl(val) : tpl.replace("{label}", val);
const ceilToNiceMax = (val) => {
if (val <= 10) return 10;
const digits = Math.floor(Math.log10(val));
const base = Math.pow(10, digits);
return Math.ceil(val / base) * base;
};
const buildChart = ($el, labels, values) => {
const maxVal = ceilToNiceMax(Math.max(...values));
let html = "";
labels.forEach((label, idx) => {
const val = values[idx];
const percent = Math.round((val / maxVal) * 100);
const colorClass =
(settings.colors && settings.colors[idx]) || settings.color;
const isBootstrap = [
"primary",
"secondary",
"success",
"danger",
"warning",
"info",
"light",
"dark",
].includes(colorClass);
const barStyle = isBootstrap
? `progress-bar bg-${colorClass}`
: "progress-bar";
const barColor = isBootstrap ? "" : `background-color: ${colorClass};`;
html += `
<div class="mb-2">
<div class="d-flex justify-content-between">
<span class="fw-semibold text-dark">${formatText(
settings.label,
label
)}</span>
<small>${formatText(settings.value, val)}</small>
</div>
<div
class="progress"
role="progressbar"
aria-valuenow="${percent}"
aria-valuemin="0" aria-valuemax="100"
data-bs-toggle="popover"
data-bs-placement="${settings.popover.placement}"
title="${formatText(settings.popover.title, label)}"
data-bs-content="${formatText(settings.popover.content, val)}"
>
<div
class="${barStyle}"
style="width: ${percent}%; ${barColor}"
></div>
</div>
</div>
`;
});
// Ruler logic
html +=
'<div class="d-flex justify-content-between px-1 text-muted small mt-2">';
if (Array.isArray(settings.rulerPoints)) {
settings.rulerPoints.forEach((val) => {
html += `<span>${val.toLocaleString()}</span>`;
});
} else {
const step = maxVal / (settings.rulerPoints - 1);
for (let i = 0; i < settings.rulerPoints; i++) {
html += `<span>${Math.round(i * step).toLocaleString()}</span>`;
}
}
html += "</div>";
$el.html(html);
// Popover setup (one active at a time)
const popoverTriggerList = [].slice.call(
$el.find('[data-bs-toggle="popover"]')
);
let activePopover = null;
popoverTriggerList.forEach((el) => {
const popover = new bootstrap.Popover(el);
el.addEventListener("click", () => {
if (activePopover && activePopover !== popover) activePopover.hide();
activePopover = popover;
});
});
document.addEventListener("click", (e) => {
if (!e.target.closest('[data-bs-toggle="popover"]') && activePopover) {
activePopover.hide();
activePopover = null;
}
});
};
return this.each(function () {
const $el = $(this);
if (settings.data) {
buildChart($el, settings.data.labels, settings.data.values);
} else if (settings.ajax) {
const ajaxData =
typeof settings.ajax.data === "function"
? settings.ajax.data()
: settings.ajax.data;
$.get(settings.ajax.url, ajaxData, (resp) => {
if (!resp.success) return;
const labels = resp.data.labels;
const values = resp.data.values;
buildChart($el, labels, values);
});
}
});
};
})(jQuery);