const { createCanvas } = require("canvas"); const Chart = require("chart.js/auto"); const logger = require("../utils/logger"); const { backgroundColors, borderColors } = require("./canvas-colors"); const { isObject, defaultsDeep, isNumber } = require("lodash"); let isProcessing = false; const requestQueue = []; const processCanvasRequest = async (req, res) => { try { const { w, h, values, keys, override } = req.body; logger.log("inbound-canvas-creation", "debug", "jsr", null, { w, h, values, keys, override }); // Set dimensions with defaults const width = isNumber(w) ? w : 500; const height = isNumber(h) ? h : 275; const configuration = { type: "doughnut", data: { labels: keys, datasets: [ { data: values, backgroundColor: backgroundColors, borderColor: borderColors, borderWidth: 1 } ] }, options: { animation: false, devicePixelRatio: 4, responsive: false, maintainAspectRatio: true, circumference: 180, rotation: -90, plugins: { legend: { labels: { boxWidth: 20, font: { family: "'Montserrat'", size: 10, style: "normal", weight: "normal" } }, position: "left" } } } }; // If we have a valid override object, merge it with the default configuration object. // This allows for you to override the default configuration with a custom one. const defaults = () => { if (!override || !isObject(override)) { return configuration; } return defaultsDeep(override, configuration); }; // Generate chart let canvas = createCanvas(width, height); let ctx = canvas.getContext("2d"); let chart = new Chart(ctx, defaults()); const result = canvas.toDataURL(); chart.destroy(); canvas.width = 0; canvas.height = 0; ctx = null; canvas = null; chart = null; res.status(200).send(result); } catch (error) { if (chart) chart.destroy(); if (canvas) { canvas.width = 0; canvas.height = 0; } ctx = null; canvas = null; chart = null; logger.log("inbound-canvas-creation", "error", "jsr", null, { error: error.message, stack: error.stack }); res.status(500).send("Error generating canvas"); } }; const processNextInQueue = async () => { if (requestQueue.length === 0) { isProcessing = false; return; } const { req, res } = requestQueue.shift(); await processCanvasRequest(req, res); processNextInQueue(); }; exports.canvastest = function (req, res) { res.status(200).send("OK"); }; exports.canvas = async function (req, res) { if (isProcessing) { if (requestQueue.length >= 100) { // Set a maximum queue size return res.status(503).send("Server is busy. Please try again later."); } requestQueue.push({ req, res }); logger.log("inbound-canvas-creation-queue", "debug", "jsr", null, { queue: requestQueue.length }); return; } isProcessing = true; await processCanvasRequest(req, res); processNextInQueue(); };