I’ve been stuck a few days on the first project on the D3 module. For whatever reason if I create an array of values, D3 won’t display it. If I console log the array and paste it directly into D3 it displays no problem. I’m guessing something in the array isn’t formatted correctly since it originated from JSON? Any hints would be appreciated.
I’m trying to use this method of extracting the data from the array since this was what was demonstrated in the course instructions, I know D3 has its own method of getting data.
let dataFromAPI=""
let data =[]
document.addEventListener('DOMContentLoaded', function(){
const req = new XMLHttpRequest();
req.open("GET",'https://raw.githubusercontent.com/freeCodeCamp/ProjectReferenceData/master/GDP-data.json',true);
req.send();
req.onload = function(){
dataFromAPI = JSON.parse(req.responseText);
for(let i in dataFromAPI.data) {
data.push(dataFromAPI.data[i][1])
}
};
});
d3.select("a").selectAll("div")
.data(data)
.enter()
.append("div")
.attr("class", "bar")
.style("height", (d) => (d/10 + "px"))
async functions are called after sync functions by default in javascript, so even though your event listener is written before the data plotter function, it’s called afterwards because its async. It’s doing something that requires waiting for something. So in your code, the data plotter function is called, then the event listener retrieves the data. So following your code style, it’s probably easiest to nest the data plotter function inside the event listener.
A more common pattern, is to define all functions including async functions at the start of the script, without calling them. Then call your async getData function which returns the data, and then chain the function with the then() keyword, which is basically a container for sync code you want called after the async function. If you are chaining functions, make sure they return data you want passed onto the next.
It’s definitely an async issue. The d3.select() is executing before the async request function returns and is operating on an empty array. You can use await/async or chains with then() (or any other async handling method) to wait for the data before drawing the graph or you can just move your graph drawing d3.select() into the req.onload function which will have data filled.
When you enter the data and it works, that’s often a hint that you’re having an async issue.
Thanks so much, that totally makes sense and why I can view the array after but D3 can’t display it. I’ll see if I can get the async request to execute before the d3.select function!
Thanks guys for the help! I took a break from D3 and went back and did the new and improved HTML section and a few other things. I started again on D3, got farther this time (with your help) then got stuck again. This time, I am able to get things to display, able to get the bars working, but now I’m trying to get the dreaded axis & ticks going.
I got the left side to show up and the values, problem is every single tick value shows up when I add them in like this
That’s because using .tickValues() with the data set tells D3 to add a tick at every data point. You can safely let D3 decide on your ticks for you.
As for your x-axis, try to get any axis to display. You can manually set the min and max for the scale if necessary to simplify things. You will have to translate it to the bottom of the graph to see it, which will mean a y transform of nearly the entire height of the visualization. I suggest experimenting with the linear scale by using it to draw the bars to verify its suitability for purpose.
So I was fighting this so long – just using my yScale I saw ‘0’ for every tick which is why I went down the ‘tickValues’ route and tried to manually format the axis. Turns out I just had to transform it farther over and the whole number (1,000, 5,000 etc) is visible.
I realize that the tooltip portion of my code might cause an error if there are duplicate GDP entries. I basically solved the last two tooltip tests this by reverse looking up the ‘i’ entry (which seems to be the GDP of the box – I’m not totally sure why) in the json data.
My question is: is there a better way to find the actual index of the box you are looking at? I think indexOf finds the first entry. If there are duplicate entries you’d be grabbing the data from the wrong array entry.
That’s what you told it to do by creating bars from gdpValues as below. D3 iterates over the iterable you gave it. And even though you are calling the mouseover callback with d and i, D3 is placing the mouse event in d and the current datum (from gdpValues) in i. The (datum, index) => {...} callback convention was for older versions of D3; current versions use (event, datum) => {...} callbacks for mouse events.
To get the correct tooltip always, you have to process your data differently. There is no reason to split the GDP and date data into two arrays as it is just as easy to process it into an array of objects, each containing the GDP and its associated date. Then when you iterate over the data to create the bars, both data points are available without any additional lookups.
The (datum, index) => {...} callback convention was for older versions of D3; current versions use (event, datum) => {...} callbacks for mouse events.
Got it, this was tripping me up. I’m going to keep messing around with my code to get it to work right.
I’ll try to process the data into an array of objects. I split it into two arrays because I was trying to simplify it for my own brain to try and wrap around which is what was getting me into trouble.
I was able to redo my code to process the data into an array of objects and access data for the tooltip directly from where the cursor is pointed. Using this (event, datum) => {...} you can access the date & gdp data using this format:
Accessing the date:
i[0]
Accessing the GDP of what you are pointing the cursor at:
I’m working on the scatterplot for a bit. I’m creating an array of arrays with Years/Times. Is there a less hooptie way of achieving this? This does what I want it to do but I’m assuming there’s a more professional way to code this.