Deploy Vega rendering API on Linux
By Mirek on (tags: Vega, categories: None)Vega js is an amazing, underrated visualization engine. Today I’ll describe few issues you might encounter when you want to deploy its rendering capabilities on Linux based environment.
Since Vega is written in Javascript we are going to deploy a Node.js based environment for that. The main purpose of the service will be the rendering capabilities of the Vega API, which is well described in Vega documentation. Additionally, we are going to expose that functionality as Azure Function hosted on Linux. As it turned out some extra requirements must be fulfilled to make this approach working. But first things first. Let’s create some context for this story.
Start new Visual Studio Code and follow this Microsoft quide to create new Azure Function project from the template. When its about to create azure resources remember to choose Node js as runtime stack and Linux as operating system. Also give the function name: Render. Once you’ve done that you should see following file structure in your project
Go to index.js file and replace its content with following
const vega = require('vega');
module.exports = async function (context, req) {
try {
const vview = new vega.View(vega.parse(req.body), { renderer: 'none' });
let svg = await vview.toSVG(2);
context.res = {
body: svg,
contentType: 'image/svg+xml'
};
}
catch (error) {
context.log(`Error during rendering vega chart: ${error}.`);
context.res = {
body: `Error during rendering vega chart: ${error}.`,
status: 500
};
}
}
Simple enough, just what the Vega API reference says about using Vega API in Node.js application.
Then go to package.json file and add Vega package in dependency section.
"dependencies": {
"vega": "^5.22.1"
}
After that open the terminal in current directory and run
npm install
Now the funny part comes in. Below are the issues and challenges I encountered during further deployment of from that stage.
1. Canvas package dependency
Vega js silently requires node-canvas package on server side deployments (no browser environment) and doesn’t complain when that package is missing. What’s the result? Well simply incorrectly renderd charts and no errors! For proof go to node_modules folder in the project and find vega-canvas package. This is a wrapper that vega uses to either use web browser DOM canvas or node-canvas object. Go to its README.md file where you can read:
To ensure error-free build processes for client-side code, this module does not include any direct or optional dependencies on the node-canvas library. Projects that use this pacakge and require canvas support for server-side (node.js) operations must include a canvas dependency in their own `package.json` file.
Ok then lets add that package to project dependencies as well, since we know we are going to deploy Vega server side
"dependencies": {
"canvas": "^2.10.2"
"vega": "^5.22.1"
}
2. Non existed fonts problem
Using non standard fonts in Vega specification may cause the chart is improperly rendered. Since we are deploying to Linux environment, there is no guarentee that the font we want to use is available there. This is a problem I encounter recently and the font I used in the chart specification was Arial Narrow which seemed no to be any special font familly, but it turned out, it was missing anyway. The result was ackward and I described it all in a github issue here.
So far I haven’t found any better solution to that missing fonts problem, except including required font in the project itself. Assuming we want to use mentioned Arial Narrow font, we need to add specific node packages to the package.json file.
"dependencies": {
"@canvas-fonts/arial-narrow": "1.0.4",
"@canvas-fonts/arial-narrow-bold": "1.0.4",
"canvas": "^2.10.2"
"vega": "^5.22.1"
}
Then in the code wee need to register that fonts for canvas as these are not system fonts and cannot be discovered automatically
const vega = require('vega');
const canvas = require('canvas');
canvas.registerFont(require("@canvas-fonts/arial-narrow"), { family: "ArialNarrow", weight: "normal" });
canvas.registerFont(require("@canvas-fonts/arial-narrow-bold"), { family: "ArialNarrow", weight: "bold" });
module.exports = async function (context, req) {
//rest function code here
Important note here: in the registerFont method the family names must not include space. So instead of “Arial Narrow” you must type “ArrialNarrow”. Strange, especially they claim this is a CSS notation, but that’s how it is.
3. CI/CD Deployment architecture problem
The canvas package caused one more problem. As it depends on some native libraries, it’s distributed with set of these libraries compiled for linux, MacOs and Windows. However for some reason when the project is built (basically npm install is called) on Windows, then zipped and deployed to linux, the canvas package fails to run. The error message
canvas.node: invalid ELF header
indicates the package was built for architecture that is different from the runtime. Now the fix for that is quite obvious, if we want to deploy to linux we must build on linux. For example if we want to deploy the project to linux docker container we can do it running Visual Studio Code in WSL remote mode as I described in one of my last post.
If we are using for example, DevOps pipeline to CI/CD our project and deploy to linux Azure Function we simply need to set the default build agent in our yaml file to linux one:
pool:
vmImage: ubuntu-latest
for more details see my last post CI/CD your Node js Azure Function with DevOps.
Cheers!