Description
Dear,
we are integrating the powerful Plotly library into the open-source Node-RED iot framework, which is running on NodeJs.
Running Plotly on the NodeJs server (via JsDom) works fine, but calling the validate
function fails.
A small program to reproduce the problem (remark: the jsdom and plotly.js-dist NPM packages need to be installed):
const jsdom = require('jsdom');
const vm = require('vm');
const fs = require('fs');
var plotlyServerDom = new jsdom.JSDOM('', { runScripts: 'dangerously'});
// Mock a few things that JSDOM doesn't support out-of-the-box
plotlyServerDom.window.HTMLCanvasElement.prototype.getContext = function() { return null; };
plotlyServerDom.window.URL.createObjectURL = function() { return null; };
// Run Plotly inside Jsdom
var plotlyJsPath = require.resolve("plotly.js-dist");
var plotlyJsSource = fs.readFileSync(plotlyJsPath, 'utf-8');
plotlyServerDom.window.eval(plotlyJsSource);
var data = [];
var trace ={};
trace.name = "mytrace";
trace.type = "scatter";
trace.x = ["2020-09-06 09:10:49"];
trace.y = [5];
trace.opacity = 1;
data.push(trace);
var layout = {};
layout.title = {};
layout.title.text = "mytitle";
var result = plotlyServerDom.window.Plotly.validate(data, layout);
if (result) {
console.log(result);
}
else {
console.log("Validation ok");
}
The result is:
[
{
code: 'object',
container: 'layout',
trace: null,
path: '',
astr: '',
msg: 'The layout argument must be linked to an object container'
},
{
code: 'object',
container: 'data',
trace: 0,
path: '',
astr: '',
msg: 'Trace 0 in the data argument must be linked to an object container'
}
]
Which means that Plotly doesn't recognize my input parameters as Javascript objects, which is checked in the isPlainObject function:
The second check fails, so the input is not considered as a Javascript object...
Seems that the problem is caused by this:
- JsDom runs its own vm (virtual machine) in NodeJs.
- That vm gets its very own instance of Object.
- The prototype of an object created inside the vm will not be the exact equal to the prototype of the Object outside the vm (i.e. in my program).
- So the equality in the IF condition will fail ...
You can find a prove of this theory in the next program. Here a global function createObject
is declared inside the vm, and called from outside the vm. This allows us to create an empty Javascript object inside the vm, return it back to us so we can add data to the object, and then we pass the object to Plotly (which is running in the Jsdom vm):
const jsdom = require('jsdom');
const vm = require('vm');
const fs = require('fs');
var plotlyServerDom = new jsdom.JSDOM('', { runScripts: 'dangerously'});
// Mock a few things that JSDOM doesn't support out-of-the-box
plotlyServerDom.window.HTMLCanvasElement.prototype.getContext = function() { return null; };
plotlyServerDom.window.URL.createObjectURL = function() { return null; };
// Script to add global functions (to create a Javascript object) in the server DOM context
const script = new vm.Script(`
function createObject() {
return {};
}
`);
// Execute the script in the VM (of jsDom), so that the global function is added to the VM context
const serverDomVmContext = plotlyServerDom.getInternalVMContext();
script.runInContext(serverDomVmContext);
var plotlyJsPath = require.resolve("plotly.js-dist");
var plotlyJsSource = fs.readFileSync(plotlyJsPath, 'utf-8');
plotlyServerDom.window.eval(plotlyJsSource);
var data = [];
var trace = plotlyServerDom.window.createObject();
trace.name = "mytrace";
trace.type = "scatter";
trace.x = ["2020-09-06 09:10:49"];
trace.y = [5];
trace.opacity = 1;
data.push(trace);
var layout = plotlyServerDom.window.createObject();
layout.title = plotlyServerDom.window.createObject();
layout.title.text = "mytitle";
var result = plotlyServerDom.window.Plotly.validate(data, layout);
if (result) {
console.log(result);
}
else {
console.log("Validation ok");
}
And that works fine, and the validation is succesful.
We could use this latter program as a workaround, but it becomes harder when the number of nested levels increases. Because all objects that we receive from anywhere in the NodeJs application, we need to create an new object for and copy all the properties. Which is of course not a very neat solution ...
So we would like to ask if it is possible to remove the second condition from the IF statement, since we think that the first condition is enough:
Thanks for your time!
Bart Butenaers