D3 treemap call resulting in NaN x0, x1, y0, y1 values - javascript

I'm building a D3 treemap visualization, and I've run into a spot of trouble.
I've been able to populate my initial treemap, but I'd like to introduce modifications to it via click interactions. In order to do that, I need to recompute a new layout (and then do some things with it later) every click, using a subset of my previous (currently displayed) data to do that.
I've written a sum function that works as I want it to, and populates the value property of each Node in my new_root object. This is, as I understand it, the sole prerequisite for running d3.treemap on my new_root Here's a snippet of the code:
let treemap = d3.treemap()
.tile(d3.treemapResquarify)
.size([800, 400])
.round(true)
.paddingInner(1);
// later...
function object_is_direct_child(root, obj) {
return (root.children.filter(child => (child.id === obj.name)).length > 0);
}
// ...much later...
// Create and bind the click event.
let click = function(d) {
if (d.data.children.length > 0) {
// Use the hover ID to get the underlying name attr (e.g. "Noise-hover" -> "Noise")
let name = this.getAttribute("id").slice(0,-6);
// Select the new root node.
let new_root = root.children.find(c => c.id === name);
// Sum.
new_root = new_root.sum(node => ((object_is_direct_child(new_root, node)) ? node.n : 0));
// Here is the problem area. This doesn't work as expected.
treemap(new_root);
}
}
hover_rects.on("click", click);
This should populate the computed, liad-out x0, x1, y0, y1 values on new_node and all of its sub-children. Instead, new_node and all of its children are NaN.
I've uploaded what I have so far as a Gist, accessible here.

I was able to debug this using the follow MWE script, run in Node:
'use strict';
// Load data.
const tree = require('../threshold-tree');
const d3 = require('d3');
const assert = require('assert');
// Load in the data file as it would be loaded in the browser.
const fs = require('fs');
const csvString = fs.readFileSync('data/complaint_types.csv').toString();
const raw_data = d3.csvParse(csvString);
let tr = new tree.ThresholdTree(raw_data);
let hr = tr.as_hierarchy();
let root = d3.stratify().id(d => d.name).parentId(d => d.parent)(hr);
function object_is_direct_child(root, obj) {
return (root.children.filter(child => (child.id === obj.name)).length > 0);
}
// Now the sum function itself.
function nodal_summer(node) {
return ((object_is_direct_child(root, node)) ? node.n : 0);
}
root = root.sum(nodal_summer);
// Create our treemap layout factory function.
let treemap = d3.treemap()
.tile(d3.treemapResquarify)
.size([800, 400])
.round(true)
.paddingInner(1);
// Apply our treemap function to our data.
debugger;
treemap(root);
// Again...this is where it fails!
let new_root = root.children[0];
new_root.parent = null;
new_root = new_root.sum(node => ((object_is_direct_child(new_root, node)) ? node.n : 0));
debugger;
new_root = treemap(new_root);
console.log(treemap(new_root));
The culprit is the positionNode callback inside d3:
function positionNode(node) {
var p = paddingStack[node.depth],
...
paddingStack is initialized as a length-1 list, but node.depth is 1, so p is initialized as NaN, which gets propagated from there.
Two working fixes are:
Using node.copy().
new_root.eachBefore(function(node) { node.depth--; }).

Related

Plotly.js: Issue graphing two-type chart from CSV files

I've taken this Plotly Chart Studio .js code and have been trying to replace the x and y values so they are taken from CSV files.
var url1 ='https://raw.githubusercontent.com/cherryleh/testcsvs/main/RS01_ETaverage.csv';
var url2 = 'https://raw.githubusercontent.com/cherryleh/testcsvs/main/RS01_ETmonthlyavg.csv';
function makeplot() {
Plotly.d3.csv(url1, function(data){ processData(data) } );
Plotly.d3.csv(url2, function(data){ processData(data) } );
};
var x1 = [], y1 = [], x2 = [], y2 = [];
function processData(allRows) {
for (var i=0; i<allRows.length; i++) {
row = allRows[i];
if (row['Month1'] !== undefined) {
x1.push(row['Month1']);}
if (row['ET1'] !== undefined) {
y1.push(row['ET1']);}
if (row['Month2'] !== undefined) {
x2.push(row['Month2']);}
if (row['ET2'] !== undefined) {
y2.push(row['ET2']);}
}
trace1.x = x1;
trace1.y = y1;
trace2.x = x2;
trace2.y = y2;
console.log(trace1.y);
}
Whole code: https://jsfiddle.net/t2q8fzxn/
When I console log the x and y values they work fine, but the graph remains empty. Any thoughts to why?
In your code, when you call your function makeplot which in turn calls the function processData, even though you modify the x and y properties of your objects trace1 and trace2 inside the processData function, these changes are only local to the processData function.
Once you are outside of the processData function, your trace1 and trace2 objects no longer have the x and y properties. And since your plotting code is outside of the scope of your processData function, your trace objects no longer have the properties x and y which is why your plots are blank.
If you want to see this for yourself, try running the following code after you run makeplot() and you'll see that the properties of the traces aren't changed:
makeplot();
console.log("here are the traces after makeplot has been run")
console.log(trace1)
console.log(trace2)
// the traces won't have the properties x or y
To get your plot to display correctly, what I did is make your processData function call another function called makePlotly which takes trace as an argument (similar to this example from the documentation). The structure of the function calls looks like this:
function processData(allRows) {
// process your csvs
// set properties for trace1 or trace2 depending on url1 or url2
// call a separate plotting function to plot each trace individually
var trace1 = {...}
var trace2 = {...}
if (condition) {
makePlotly(trace1)
}
else {
makePlotly(trace2)
}
function makePlotly(trace) {
var data = [trace];
var layout = {...}
Plotly.plot('plotly-div', data, layout);
}
To avoid any issues about the scope of trace1 and trace2, I defined them inside the processData function (it seems unnecessary to define the traces outside of the function since they won't be reused anyway). You can find my fiddle here, which produces the following plot.

Get line number with abstract syntax tree in node js

Im making a program that takes some code via parameter, and transform the code adding some console.logs to the code. This is the program:
const escodegen = require('escodegen');
const espree = require('espree');
const estraverse = require('estraverse');
function addLogging(code) {
const ast = espree.parse(code);
estraverse.traverse(ast, {
enter: function(node, parent) {
if (node.type === 'FunctionDeclaration' ||
node.type === 'FunctionExpression') {
addBeforeCode(node);
}
}
});
return escodegen.generate(ast);
}
function addBeforeCode(node) {
const name = node.id ? node.id.name : '<anonymous function>';
const beforeCode = "console.log('Entering " + name + "()');";
const beforeNodes = espree.parse(beforeCode).body;
node.body.body = beforeNodes.concat(node.body.body);
}
So if we pass this code to the function:
console.log(addLogging(`
function foo(a, b) {
var x = 'blah';
var y = (function () {
return 3;
})();
}
foo(1, 'wut', 3);
`));
This is the output of this program:
function foo(a, b) {
console.log('Entering foo()');
var x = 'blah';
var y = function () {
console.log('Entering <anonymous function>()');
return 3;
}();
}
foo(1, 'wut', 3);
And this is the AST (Abstract Syntax Tree) for that last function passed to addLoggin:
https://astexplorer.net/#/gist/b5826862c47dfb7dbb54cec15079b430/latest
So i wanted to add more information to the console logs like for example the line number we are on. As far as i know, in the ast, the node has a value caled 'start' and 'end' which indicates in which character that node starts and where it ends. How can i use this to get the line number we are on? Seems pretty confusing to me to be honest. I was thinking about doing a split of the file by "\n", so that way i have the total line numbers, but then how can i know i which one im on?
Thank you in advance.
Your idea is fine. First find the offsets in the original code where each line starts. Then compare the start index of the node with those collected indexes to determine the line number.
I will assume here that you want the reported line number to refer to the original code, not the code as it is returned by your function.
So from bottom up, make the following changes. First expect the line number as argument to addBeforeCode:
function addBeforeCode(node, lineNum) {
const name = node.id ? node.id.name : '<anonymous function>';
const beforeCode = `console.log("${lineNum}: Entering ${name}()");`;
const beforeNodes = espree.parse(beforeCode).body;
node.body.body = beforeNodes.concat(node.body.body);
}
Define a function to collect the offsets in the original code that correspond to the starts of the lines:
function getLineOffsets(str) {
const regex = /\r?\n/g;
const offsets = [0];
while (regex.exec(str)) offsets.push(regex.lastIndex);
offsets.push(str.length);
return offsets;
}
NB: If you have support for matchAll, then the above can be written a bit more concise.
Then use the above in your main function:
function addLogging(code) {
const lineStarts = getLineOffsets(code); // <---
let lineNum = 0; // <---
const ast = espree.parse(code);
estraverse.traverse(ast, {
enter: function(node, parent) {
if (node.type === 'FunctionDeclaration' ||
node.type === 'FunctionExpression') {
// Look for the corresponding line number in the source code:
while (lineStarts[lineNum] < node.body.body[0].start) lineNum++;
// Actually we now went one line too far, so pass one less:
addBeforeCode(node, lineNum-1);
}
}
});
return escodegen.generate(ast);
}
Unrelated to your question, but be aware that functions can be arrow functions, which have an expression syntax. So they would not have a block, and you would not be able to inject a console.log in the same way. You might want to make your code capable to deal with that, or alternatively, to skip over those.

How to pass integer values in cucumber test and verify the result

How do I call a simple addition function and assert the result of two values using selenium-cucumber-js framework with a test written below. While running the below it says
TypeError: TypeError: Cannot read property 'addvalues' of undefined
at createWorld.When (C:\Tests\cucumber\step-definitions\addvalues-steps.js:5:25)
Feature:
Scenario: Addition of two values
When Add two values 5 and 10
Then I should get result 15
// Here is my 'addvalues-steps.js' file
const expect = require('chai').expect;
module.exports = function () {
this.When(/^Add two values (-?\d+) and (-?\d+)$/, (x, y) =>{
this.page.addvalues.addValues(x,y);
})
this.Then(/^I should get result (-?\d+)$/, (ans) =>{
let tot = this.page.addvalues.addValues(x, y);
expect(tot).to.be.eql(ans);
})
};
// Following is my 'addvalues.js file'
module.exports = {
addValues(x,y){
var total = x + y ;
return total ;
}
};
// world.js >>
const { CustomWorld } = require('cucumber')
function CustomWorld() {
console.log('overriding the world')
this.page = {
addvalues: require('../page-objects/addvalues')
}
console.log("This is the recent error log:"+this.page.addvalues)
}
module.exports = function() {
this.World = CustomWorld;
Note: the below example is for an old version of cucumber-js: 1.3.3.
With cucumber.js, when you're referencing this from inside step definitions, you're actually referencing the World context. So, for this.page.addvalues.addValues(x,y); to work properly, you'll first need to create page that has a reference to your addvalues.js. Something along these lines:
world.js:
function CustomWorld() {
console.log('overriding the world')
this.page = {
addvalues: require('../page-objects/addvalues')
}
}
module.exports = function() {
this.World = CustomWorld;
};
addvalues.js:
//addvalues.js
module.exports = {
addValues(x,y){
var total = x + y ;
return total ;
}
};
There's also a couple of things to correct in your steps.js.
Don't pass arrow functions into the steps, as this will remove the this context that you're setting in World.js.
If you want to share variables between steps (as you do in your example), you need to store them somewhere. One such place, again, would be the World context. Note how in my version I set this.prevResult
When the variables are injected into your steps, they are injected as strings. Note the parseInt() in my version.
addvalues-steps.js:
const expect = require('chai').expect;
module.exports = function() {
this.When(/^Add two values (-?\d+) and (-?\d+)$/, function (x, y) {
this.prevResult = this.page.addvalues.addValues(parseInt(x, 10), parseInt(y, 10));
})
this.Then(/^I should get result (-?\d+)$/, function (ans) {
let tot = this.prevResult;
expect(tot).to.be.eql(parseInt(ans, 10));
})
}
UPD: It turns out that the question is about selenium-cucumber-js, which is a framework on top of cucumber-js. Disregard the comments about the world.js.
According to selenium-cucumber-js docs, you don't need this to access the page objects in your step definitions:
Page objects are accessible via a global page object and are
automatically loaded from ./page-objects.
const expect = require('chai').expect;
module.exports = function() {
this.When(/^Add two values (-?\d+) and (-?\d+)$/, function (x, y) {
this.prevResult = page.addvalues.addValues(parseInt(x, 10), parseInt(y, 10));
})
this.Then(/^I should get result (-?\d+)$/, function (ans) {
let tot = this.prevResult;
expect(tot).to.be.eql(parseInt(ans, 10));
})
}

How to increment and set an id for each node using javascript OOPS concept?

I am creating a Mind Mapping software using raphael.js and javascript.
I want to create a set of nodes where a node is like a rectangle and has its attributes as id, x,y coordinates etc. There is a plus icon on the rectangle on click of which a new node/rectangle is created with its new unique id. I want to create node and assign each created node its id in an Object Oriented fashion.
I am having difficulty in assigning a unique id to each node on its creation. I want to set ids starting from 0,1,2,3...
Second difficulty is selecting a node. I want to select a node based on its id.
Please have a look at the following code.
Could someone please help me in assigning ids to each of the nodes and to select each node based on its id?
assume that there is a canvas of 1000px width and 700px height.
paper = Raphael("canvas", 1000,700);
// create.js
drawNode(290, 80);
function drawNode(x,y)
{
id=0;
a = new node(x,y,id);
a.drawNode();
}
// nodes.js
var node = function(x,y,id){
this.id=id;
this.x=x;
this.y=y;
this.drawNode = function(){
var st = paper.set();
a = paper.rect(this.x,this.y, 100, 40, 2);
a.add = paper.image('images/plus.png', this.x+77, this.y+12, 20, 20)
a.attr({fill: '#8EDFF0', stroke: '#6AB0F2'});
st.push(a,a.text,a.add,a.addnote);
a.add.click(this.add);
}
this.add = function () {
id=1;
a = new node(this.attrs.x+150, this.attrs.y,id);
a.drawNode();
}
}
Please tell me how can I set unique id to each node instead of hardcoding the values and how to do that.
You, probably, need some NodeCostructor singleton with id parameter set to 0 from the start and createNode as function that accepts x and y, uses NodeCostructor's id to draw a node and returns you a node, than increases NodeCostructor's id value. A basic example of how should it be (EDIT: I've found some js singleton tutorial, so this new code will be more valid in terms of OOP):
var NodeConstructor = (function() {
var instance = null;
function NCInstanceCreate() {
var id=0;
createNode = function(x,y) {
tmp = new node(x, y, this.id);
this.id++;
return tmp;
}
return {
createNode : createNode,
id : id
}
}
function getInstance() {
if( ! instance ) {
instance = new NCInstanceCreate();
}
return instance;
}
return {
getInstance : getInstance
};
})(window);
var node = function(x,y,id){
this.id=id;
this.x=x;
this.y=y;
}
myConstructor=NodeConstructor.getInstance();
console.log(myConstructor.createNode(100,200));
console.log(myConstructor.createNode(110,220));
console.log(myConstructor.createNode(130,240));
console.log(myConstructor.id);
So you basically should use myConstructor.createNode(x,y) instead of new node(x,y,id) whenever you want to create node.

D3 dispatch pass in arguments and calling context

I understand that in D3, dispatch can be used to fire events to multiple visualisations according to this example.
I also understand that if I want to call a dispatch from an object and pass in the context, I can use apply as shown here.
However, I'm having a hard time combining the arguments from a D3 dispatch and the context that I want.
// create my dispatcher
var probeDispatch = d3.dispatch("probeLoad");
var line_count = 0;
// load a file with a bunch of JSON and send one entry every 50 ms
var lines = [[0,1],[1,2],[2,0]];
var parse_timer = window.setInterval(
function () {
parse_dispatch();
}, 50
);
function parse_dispatch(){
// send two arguments with my dispatch
probeDispatch.probeLoad(lines[line_count][0], lines[line_count][1]);
line_count += 1;
if(line_count >= lines.length){
//line_count = 0
window.clearInterval(parse_timer);
}
}
// my chart object
var genChart = function(label){
this.label = label;
// assume I've drawn my chart somewhere here
probeDispatch.on(("probeLoad."+this.label), this.probeParse);
// this next line isn't working, since the
// console.log in probeLoad still returns undefined
probeDispatch.probeLoad.apply(this);
};
genChart.prototype = {
probeParse: function(probeData, simTime) {
// How do I get the context from the object that's calling probeParse
// into the probeParse scope?
var self = this;
console.log(self.label);
}
};
new genChart("pants");
new genChart("shirt");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
It does set the context properly when you see "pants" in the console.
But then there are 3 undefined's logged, because you also call
// send two arguments with my dispatch
probeDispatch.probeLoad(lines[line_count][0], lines[line_count][1]);
without supplying context.
You need
probeDispatch.probeLoad.apply(instanceOfGenChart, [lines[line_count][0], lines[line_count][1]]);
But enabling that also requires moveing parse_dispatch down the page.
// create my dispatcher
var probeDispatch = d3.dispatch("probeLoad");
var line_count = 0;
// load a file with a bunch of JSON and send one entry every 50 ms
var lines = [[0,1],[1,2],[2,0]];
var parse_timer = window.setInterval(
function () {
parse_dispatch();
}, 50
);
// my chart object
var genChart = function(label){
this.label = label;
// assume I've drawn my chart somewhere here
probeDispatch.on(("probeLoad."+this.label), this.probeParse);
// this next line isn't working, but I don't know what to do
probeDispatch.probeLoad.apply(this);
};
genChart.prototype = {
probeParse: function(probeData, simTime) {
// How do I get the context from the object that's calling probeParse
// into the probeParse scope?
var self = this;
console.log(self.label);
}
};
var instanceOfGenChart = new genChart("pants");
function parse_dispatch(){
// send two arguments with my dispatch
probeDispatch.probeLoad.apply(instanceOfGenChart, [lines[line_count][0], lines[line_count][1]]);
line_count += 1;
if(line_count >= lines.length){
//line_count = 0
window.clearInterval(parse_timer);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
So it turns out to bring the context into the function, I have to bind() it for reasons I'm not too clear on.
// create my dispatcher
var probeDispatch = d3.dispatch("probeLoad");
var line_count = 0;
// load a file with a bunch of JSON and send one entry every 50 ms
var lines = [[0,1],[1,2],[2,0]];
var parse_timer = window.setInterval(
function () {
parse_dispatch();
}, 50
);
function parse_dispatch(){
// send two arguments with my dispatch
probeDispatch.probeLoad(lines[line_count][0], lines[line_count][1]);
line_count += 1;
if(line_count >= lines.length){
//line_count = 0
window.clearInterval(parse_timer);
}
}
// my chart object
var genChart = function(label){
this.label = label;
// assume I've drawn my chart somewhere here
probeDispatch.on(("probeLoad."+this.label), this.probeParse.bind(this));
};
genChart.prototype = {
probeParse: function(probeData, simTime) {
// How do I get the context from the object that's calling probeParse
// into the probeParse scope?
var self = this;
console.log(self.label);
}
};
new genChart("pants");
new genChart("shirt");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Added by meetamit
Bind is the solution here, because it locks a scope to an "instance" of genChart.prototype. probeParse. This way parse_dispatch (the invoker) doesn't need to know anything about scope. It's equivalent to this:
// my chart object
var genChart = function(label){
this.label = label;
var self = this;
var probeParseBound = function() { self.probeParse(); };
probeDispatch.on(("probeLoad."+this.label), probeParseBound);
};

Categories