133 lines
3.4 KiB
JavaScript
133 lines
3.4 KiB
JavaScript
const { Canvas, FontLibrary } = require("skia-canvas");
|
|
const Chart = require("chart.js/auto");
|
|
|
|
const { backgroundColors, borderColors } = require("./canvas-colors");
|
|
const { defaultsDeep, isNumber } = require("lodash");
|
|
|
|
const CANVAS_QUEUE_LIMIT = 100;
|
|
|
|
let isProcessing = false;
|
|
const requestQueue = [];
|
|
|
|
try {
|
|
FontLibrary.use("Montserrat", [
|
|
"/usr/share/fonts/Montserrat-Regular.ttf",
|
|
"/usr/share/fonts/Montserrat-Bold.ttf",
|
|
"/usr/share/fonts/Montserrat-Italic.ttf"
|
|
]);
|
|
} catch (error) {
|
|
console.error(
|
|
"Error loading fonts Skia Canvas Fonts, please be sure to install Montserrat font package",
|
|
error.message
|
|
);
|
|
}
|
|
|
|
// Utility to create a chart configuration
|
|
const getChartConfiguration = (keys, values, override) => {
|
|
const defaultConfiguration = {
|
|
type: "doughnut",
|
|
data: {
|
|
labels: keys,
|
|
datasets: [
|
|
{
|
|
data: values,
|
|
backgroundColor: backgroundColors,
|
|
borderColor: borderColors,
|
|
borderWidth: 1
|
|
}
|
|
]
|
|
},
|
|
options: {
|
|
devicePixelRatio: 4,
|
|
responsive: false,
|
|
animation: false,
|
|
maintainAspectRatio: true,
|
|
circumference: 180,
|
|
rotation: -90,
|
|
plugins: {
|
|
legend: {
|
|
labels: {
|
|
boxWidth: 20,
|
|
font: {
|
|
family: "'Montserrat'",
|
|
size: 10,
|
|
style: "normal",
|
|
weight: "normal"
|
|
}
|
|
},
|
|
position: "left"
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
return defaultsDeep(override || {}, defaultConfiguration);
|
|
};
|
|
|
|
const processCanvasRequest = async (req, res) => {
|
|
const { logger } = req;
|
|
const { w, h, values, keys, override } = req.body;
|
|
|
|
logger.log("inbound-canvas-creation", "debug", "jsr", null, { w, h, values, keys, override });
|
|
|
|
// Default width and height
|
|
const width = isNumber(w) && w > 0 ? w : 500;
|
|
const height = isNumber(h) && h > 0 ? h : 275;
|
|
const configuration = getChartConfiguration(keys, values, override);
|
|
|
|
let chart = null;
|
|
|
|
try {
|
|
const canvas = new Canvas(width, height);
|
|
const ctx = canvas.getContext("2d");
|
|
|
|
chart = new Chart(ctx, configuration);
|
|
|
|
const chartImage = (await canvas.toBuffer("image/png")).toString("base64");
|
|
res.status(200).send(`data:image/png;base64,${chartImage}`);
|
|
} catch (error) {
|
|
logger.log("canvas-error", "error", "jsr", null, { error: error.message });
|
|
res.status(500).send("Error generating canvas");
|
|
} finally {
|
|
chart?.destroy();
|
|
}
|
|
};
|
|
|
|
const enqueueRequest = (req, res) => {
|
|
if (requestQueue.length >= CANVAS_QUEUE_LIMIT) {
|
|
res.status(503).send("Server is busy. Please try again later.");
|
|
return false;
|
|
}
|
|
requestQueue.push({ req, res });
|
|
req.logger.log("inbound-canvas-creation-queue", "debug", "jsr", null, { queue: requestQueue.length });
|
|
return true;
|
|
};
|
|
|
|
const processNextInQueue = async () => {
|
|
try {
|
|
while (requestQueue.length > 0) {
|
|
const { req, res } = requestQueue.shift();
|
|
try {
|
|
await processCanvasRequest(req, res);
|
|
} catch (err) {
|
|
console.error("canvas-queue-error", "error", "jsr", null, { error: err.message });
|
|
}
|
|
}
|
|
} finally {
|
|
isProcessing = false;
|
|
}
|
|
};
|
|
|
|
exports.canvastest = function (req, res) {
|
|
res.status(200).send("OK");
|
|
};
|
|
|
|
exports.canvas = async (req, res) => {
|
|
if (!enqueueRequest(req, res)) return;
|
|
|
|
if (!isProcessing) {
|
|
isProcessing = true;
|
|
processNextInQueue().catch((err) => console.error("canvas-processing-error", { error: err.message }));
|
|
}
|
|
};
|