165 lines
4.7 KiB
JavaScript
165 lines
4.7 KiB
JavaScript
const { createCanvas } = require("canvas");
|
|
const { Canvas, FontLibrary } = require("skia-canvas");
|
|
const { performance } = require("perf_hooks");
|
|
const Chart = require("chart.js/auto");
|
|
const logger = require("../utils/logger");
|
|
|
|
const { backgroundColors, borderColors } = require("./canvas-colors");
|
|
const { isObject, defaultsDeep, isNumber } = require("lodash");
|
|
|
|
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);
|
|
};
|
|
|
|
// Utility to validate input
|
|
const validateCanvasInput = ({ values, keys, override }, res) => {
|
|
if (!Array.isArray(values) || !Array.isArray(keys)) {
|
|
res.status(400).send("Invalid input: 'values' and 'keys' must be arrays.");
|
|
return false;
|
|
}
|
|
|
|
if (values.some((value) => typeof value !== "number")) {
|
|
res.status(400).send("Invalid input: 'values' must be an array of numbers.");
|
|
return false;
|
|
}
|
|
|
|
if (keys.some((key) => typeof key !== "string")) {
|
|
res.status(400).send("Invalid input: 'keys' must be an array of strings.");
|
|
return false;
|
|
}
|
|
|
|
if (override && !isObject(override)) {
|
|
res.status(400).send("Override must be an object");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
exports.canvastest = function (req, res) {
|
|
//console.log("Incoming test request.", req);
|
|
res.status(200).send("OK");
|
|
};
|
|
|
|
exports.canvas = function (req, res) {
|
|
const startTime = performance.now();
|
|
|
|
const { w, h, values, keys, override } = req.body;
|
|
//console.log("Incoming Canvas Request:", w, h, values, keys, override);
|
|
logger.log("inbound-canvas-creation", "debug", "jsr", null, { w, h, values, keys, override });
|
|
|
|
if (!validateCanvasInput(req.body, res)) return;
|
|
|
|
// Set the default Width and Height
|
|
let [width, height] = [500, 275];
|
|
|
|
// Allow for custom width and height
|
|
if (isNumber(w)) {
|
|
width = w;
|
|
}
|
|
if (isNumber(h)) {
|
|
height = h;
|
|
}
|
|
|
|
const configuration = getChartConfiguration(keys, values, override);
|
|
|
|
res.status(200).send(
|
|
(() => {
|
|
const canvas = createCanvas(width, height);
|
|
const ctx = canvas.getContext("2d");
|
|
new Chart(ctx, configuration);
|
|
return canvas.toDataURL();
|
|
})()
|
|
);
|
|
console.log("Canvas generation time:", performance.now() - startTime, "ms");
|
|
};
|
|
|
|
exports.canvasSkia = async function (req, res) {
|
|
const startTime = performance.now();
|
|
const { w, h, values, keys, override } = req.body;
|
|
|
|
// Log incoming request for debugging
|
|
logger.log("inbound-canvas-creation", "debug", "jsr", null, { w, h, values, keys, override });
|
|
|
|
if (!validateCanvasInput(req.body, res)) return;
|
|
|
|
// Default width and height
|
|
const width = typeof w === "number" && w > 0 ? w : 500;
|
|
const height = typeof h === "number" && h > 0 ? h : 275;
|
|
|
|
const configuration = getChartConfiguration(keys, values, override);
|
|
|
|
try {
|
|
// Create a canvas and get the 2D rendering context
|
|
const canvas = new Canvas(width, height);
|
|
const ctx = canvas.getContext("2d");
|
|
|
|
// Render the chart
|
|
new Chart(ctx, configuration);
|
|
|
|
// Convert the canvas to a Base64-encoded image
|
|
const chartImage = (await canvas.toBuffer("image/png")).toString("base64");
|
|
const dataURL = `data:image/png;base64,${chartImage}`;
|
|
|
|
// Send the Base64-encoded image as the response
|
|
res.status(200).send(dataURL);
|
|
console.log("Canvas generation time:", performance.now() - startTime, "ms");
|
|
} catch (error) {
|
|
// Log and handle rendering errors
|
|
logger.log("canvas-error", "error", "jsr", null, { error: error.message });
|
|
console.error("Error generating chart:", error.message);
|
|
res.status(500).send("Failed to generate canvas.");
|
|
}
|
|
};
|