D3.JSON Method Doesn't Return My Data Array

d3.json method doesn't return my data array

TL;DR

d3.json (as well as d3.csv, d3.tsv etc) does not return the content of the loaded/parsed file. Instead of that, it returns an object related to the request in D3 v4 or lower, and a promise in D3 v5 or higher.

What does d3.json return? (v4 or lower)

d3.json is one of the alternatives to XMLHttpRequest provided by D3. According to the API, d3.json...

Returns a new request to get the JSON file at the specified url with the default mime type application/json.

... which, we can agree, is not particularly clear. Because of that, you probably thought that you could return the loaded data using var data = d3.json(url, callback), but that's incorrect. What d3.json returns is an object (not an array), associated with the request. Let's see it.

I have this JSON in a file:

{"foo": "42"}

What happens if we use d3.json the way you used it in your question? Click "run code snippet" to see:

var data = d3.json("https://api.npoint.io/5b22a0474c99d3049d2e", function() {});

console.log(data)
<script src="https://d3js.org/d3.v4.min.js"></script>

D3.json function to return only some of json file data

d3.json doesn't use a row function, since json data isn't always an array. To processes the data, you can do something like:

d3.json("myfile.json").then(function (json) {
return json.map(function (d) {
return {
location: d.identifier,
date: new Date(d.created),
amount: d.count_objects
};
});
}).then(function getData(rawData) {

console.log(rawData[0]);

});

Returning array from d3.json()

Besides the fact that your problem description is very terse, the problem seems to be your assumptions about what is returning what.

The function d3.json() is an asynchronous function that directly returns (with an undefined value I assume). Only when the data is received from the backend, the callback function you passed to it will be called. Obviously the context is different here and the return value of your callback will not automatically become the return value of d3.json (as this one has returned "long" before already).

What you want to do is probably something like:

    var jsondata;
d3.json(dataPath, function(dataFromServer) {
jsondata = dataFromServer;
}
console.log(jsondata);

Update 1:
Obviously, the above example is still not fully correct. The call to console.log() is made directly after the d3.json() returned. Thus, the server might not have sent the reply fully yet. Therefore, you can only access data when the callback is returned. Fixed example:

    var jsondata;

function doSomethingWithData() {
console.log(jsondata);
}

d3.json(dataPath, function(dataFromServer) {
jsondata = dataFromServer;
doSomethingWithData();
})

For a (somewhat stupid, but) working example see: http://jsfiddle.net/GhpBt/10/

Update 2:
The above example demonstrates in which order the code is executed but does not really deserve a price for most beautiful code. I myself would not use this "global" variable and simplify the above example to:

    function doSomethingWithData(jsondata) {
console.log(jsondata);
}

d3.json(dataPath, doSomethingWithData);

Note how doSomethingWithData is directly passed to d3.json in stead of calling it separately in an anonymous inner function.

Note: This is not a particular problem of d3.js. Basically, all javascript functions that are asynchronous are likely to behave in a similar way. If they return something, it will not be the return value of the passed callback.

Json D3 array contents not working

This is related to asynchronous code.

Move your processing code inside the callback, where you currently only have:

console.log(d);

The code you will put there is executed later than any of the non-callback code you have, because the script first executes to completion and only then (asynchronously) the callback will be fired -- and thus, only then d (dataset) will have the desired value:

d3.json('/api/answers/', function (dataset) {
var w = 1000;
var h = 1000;
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
var circles = svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle");
// etc....
// ...
});

NB: your enormous if statements can be reduced to 5% of the code, if you would use arrays for your possible values.

For instance, this:

    var Xaxis;
if (i === 0) { Xaxis = "500"; }
else if (i === 1) { Xaxis = "400"; }
else if (i === 2) { Xaxis = "420"; }
else if (i === 3) { Xaxis = "452.5"; }
else if (i === 4) { Xaxis = "485"; }
else if (i === 5) { Xaxis = "515"; }
else if (i === 6) { Xaxis = "547.5"; }
else if (i === 7) { Xaxis = "580"; }
else if (i === 8) { Xaxis = "600"; }
else if (i === 9) { Xaxis = "600"; }
else if (i === 10) { Xaxis = "650"; }
else if (i === 11) { Xaxis = "700"; }
else if (i === 12) { Xaxis = "750"; }
else if (i === 13) { Xaxis = "750"; }
else if (i === 14) { Xaxis = "750"; }
else if (i === 15) { Xaxis = "750"; }
else if (i === 16) { Xaxis = "750"; }
return Xaxis;

... can be written as:

    var Xaxis = [500, 400, 420, 452.5, 485, 515, 547.5, 580, 600, 600,
650, 700, 750, 750, 750, 750, 750][i];

It seems though you want to position the smaller circles nicely around the bigger circle. In that case you can calculate the coordinates using trigonometric functions (sine and cosine) based on the value of i. It could look like this for the smaller circles (i > 0) -- the larger is positioned at [x,y] = [500,500]:

    x = 500-Math.cos((i-1)/2)*100
y = 500+Math.sin((i-1)/2)*100

Also note that you have dead code in your attr callback. The return statement will exit that function before determining a color and returning that. So you should delete that return statement.

Finally, there is a color missing, since your sample data has AnswerList values going up to 6, while you only specify colors for up to index 5.

Here is how the code could look with these changes. Note that I have used ES6 arrow functions. If you don't have ES6 support, just change them to standard functions:

var dataset = [    {"Id":1,"UserID":"JNP","HCRef":"ProjSetup","AnswerString":"","Answerlist":1},    {"Id":2,"UserID":"JNP","HCRef":"ProjSetup","AnswerString":null,"Answerlist":2},    {"Id":3,"UserID":"JNP","HCRef":"ProjSetup","AnswerString":null,"Answerlist":5},    {"Id":4,"UserID":"JNP","HCRef":"ProjSetup","AnswerString":null,"Answerlist":6},    {"Id":5,"UserID":"JNP","HCRef":"ProjSetup","AnswerString":null,"Answerlist":3},    {"Id":6,"UserID":"JNP","HCRef":null,"AnswerString":null,"Answerlist":4}];var svg = d3.select("body")    .append("svg")    .attr("width", 1000)    .attr("height", 1000)    .selectAll("circle")    .data(dataset)    .enter()    .append("circle")    .attr("cx", (d, i) => i ? 500-Math.cos((i-1)/2)*100 : 500)    .attr("cy", (d, i) => i ? 500+Math.sin((i-1)/2)*100 : 500)    .attr("r", (d, i) => i === 0 ? 30 : 20)    .attr("fill", (d) => ['', 'green', 'gold', 'darkorange', 'red', 'lightgrey', 'purple']                         [d.Answerlist]);
<script src="https://d3js.org/d3.v4.min.js"></script>Scroll down to see the colored circles.

Loading json data to an array in d3

You cannot use nest to directly have an array of values. The two possible output formats of nest are:

  • a large object

    { 
    key1: value1,
    key2: value2,
    ...
    }
  • or an array of small objects

    [ 
    { key: key1, values: value1 },
    { key: key2, values: value2 },
    ...
    ]

Neither is the one you desire. (Remember the first goal of nest: identify a set of keys, and group all pieces of data with the same key in a single batch, possibly with some transformation).

If for some reason you don't want to use your original array as suggested in the comments, then d3.map is what you're needing:

var cost = d3.map(data, function(d) {
return d.cost;
});

This is creating a copy of your cost data (if your data array changes, then you will need to run d3.map again to update your array). So you should use this array only locally if your data may not be constant. This is why in general one prefers using the original data directly, as it also saves this copy step and has less risks of tricky bugs later on.

Why can't I access my JavaScript array by index outside of the d3 then function?

The logic inside the then block is asynchronous. What that means it's scheduled to be executed when the thread has time, and the promise to read a /votes file has resolved. That means that the code is actually executed in the following order:

var votesData = [];
console.log(votesData);
console.log(votesData[0]);

//access votes route
d3.json("/votes").then(function (data) {
...
});

That is why it returns undefined, it hasn't been executed yet! The common workaround is to move all your logic inside the .then() block, or, if you're using ES2017 you can use async/await.

var votesData = [];

//access votes route
const data = await d3.json("/votes");

//loop through objects in route
data.forEach(function (d) {

//convert data to numeric
demYes = +d.democratic.yes
...
}

console.log(votesData);
console.log(votesData[0]);

What this compiles to under water is equivalent to the .then() solution, it's just meant to make your coding experience easier.

Javascript Map Won't Return Array Value, for D3.js Chart

Well, your d3.map() is correct, that's not the problem.

The problem is that your CSV doesn't have data for all counties. Therefore, sometimes, this...

unemployment.get(d.id)

... is undefined, which explains your error:

unemployment.get(d.id)[0] --> Cannot read property '0' of undefined

Solution

Check for undefined:

.attr("fill", function(d){
return unemployment.get(d.id) ? color(unemployment.get(d.id)[0]) : "#ccc";
})

Here, if unemployment.get(d.id) is undefined, the function will return #ccc. Change this according to the colour you want.

Here is the updated bl.ocks: https://bl.ocks.org/anonymous/3adc7518e4423d816f6b6842ef48d27f/414e4b738dd4c2d8e91a9ca2fb8511dcec747b9c

d3.js not able to parse json data

If you want to deal with both the data formats, the most straight forward way to do it is checking whether the data.StoreVisitGraphCount.list is an Array or an Object. Taking a cure from the question: How to check if a JSON response element is an array? , the most reliable way to do it is:

function isArray(what) {
return Object.prototype.toString.call(what) === '[object Array]';
}

Then your code would read:

d3.json(..., function(error, data) {
if (!isArray(data.StoreVisitGraphCount.list)) {
data.StoreVisitGraphCount.list = [ data.StoreVisitGraphCount.list ];
}

data.StoreVisitGraphCount.list.forEach(function(d) {
d.date = parseDate(d.date);
d.count = +d.count;
});
});

However, that is not a very consistent API design. If the API is in your control (you are running the service from localhost), then consider changing it to be consistent and return a list in every case.



Related Topics



Leave a reply



Submit