Group and count json data in queue() in d3.js - javascript

I am trying to change the format of a json file sourcing a D3 map. The original source is World Choropleth by palewire.
EDIT: working code thanks to below answer in the Plunker:
http://embed.plnkr.co/JYTATyrksAg3OJ0scHAp/
The original json is in a nested format with a count for each value:
{
"id": "IQ-G02-D009",
"rate": "1"
},
{
"id": "IQ-G05-D030",
"rate": "4"
},
{
"id": "IQ-G07-D047",
"rate": "5"
}
]
The new json will use a flat format, something like:
[
{
"id": "IQ-G02-D009"
},
{
"id": "IQ-G05-D030"
},
{
"id": "IQ-G05-D030"
},
{
"id": "IQ-G05-D047"
},
{
"id": "IQ-G07-D047"
}
]
It seems using a rollup and nest function would be the way forward to get the new json in a similar form of the former one, but am getting stuck at implementing those in a queue().
The rollup should be implemented at the .defer level it seems:
queue()
.defer(d3.json, "map.json")
.defer(d3.json, "data.json")
.await(ready);
Can I use the nest and rollup function directly in the queue?

You had some typo's working against you. The general answer to your question, as cal_br_mar said: apply nest and roll up when your data is loaded. So:
queue()
.defer(d3.json, "map.json")
.defer(d3.json, "data.json")
.await(ready)
function ready(error, country, activities) {
activities = d3.nest()
.key(function(d) {
return d.id;
})
.rollup(function(v) {
return v.length;
})
.entries(activities);
}
You had a typo in your last line: it should be activities, not expenses (wrong copy and paste, I guess).
And in the code that follows it should be:
activities.forEach(function(d) {
rateById[d.key] = +d.values;
});
The nest() and rollup puts your data in key and values properties, as you can see in your console output of the nested data:
[{"key":"IQ-G02-D009","values":1},{"key":"IQ-G05-D030","values":2},{"key":"IQ-G05-D047","values":1},{"key":"IQ-G07-D047","values":1}]
Change those fields and you should be fine.

Related

Loading json data to an array in d3

so I am trying to assign json data to an array variable in d3.
Here is my json:
[
{
"Impressions": "273909",
"Clicks": "648",
"CPM": 4.6388278388278,
"Cost": 1266.4,
"CPC": 1.9543209876543,
"Campaign": "Campaign 1"
},
{
"Impressions": "974408",
"Clicks": "14571",
"CPM": 4.0175975359343,
"Cost": 3913.14,
"CPC": 0.26855672225654,
"Campaign": "Campaign 2"
},
{
"Impressions": "76751",
"Clicks": "5022",
"CPM": 8.4675,
"Cost": 643.53,
"CPC": 0.1281421744325,
"Campaign": "Campaign 3"
},
and here is my code to load the json dataset:
d3.json("DS003a_Adwords_AdPerformance_modified.json", function(error, data) {
var topData = data.sort(function(a, b){
return d3.descending(+a.cost, +b.cost);
}).slice(0,10);
topData.forEach(function (d) {
d.CampaignName = d.Campaign;
d.cost = d.Cost;
});
var cost = d3.nest()
.key(function(d) {return d.Cost;})
.entries(data); //fail
var p = d3.select("body").selectAll("p")
.data(topData)
.enter()
.append("p")
.text(function(d,i){
return (i+1) + ". " + d.CampaignName + " cost = " + cost[i];
});
I basically want to save the value of "Cost" to an array variable var cost.
But when I tried my code the result is as followed:
What should i do?
Thank you, your help is appreciated :)
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.

How can I use jQuery to push JSON data into an array?

I'm new to jQuery, and I'm trying out the getJSON function. What I want to do is pull the "id" section of a JSON file and push it into an array called planes in jQuery. From there, the array is used in an autocomplete function to fill in the searchable IDs.
var planes = [];
$.getJSON('planes.json', function(data) {
console.log('Filling array...');
//This is where I think the issue is occurring.
//Is using the name of the section you want to use the correct syntax here?
$.each(data.id, function (index, val) {
planes.push(val.id);
console.log('Pushed ' + index);
});
});
// After getJSON, array should look something like this:
// var planes = [
// 'Alara',
// 'Fiora',
// 'Innistrad',
// 'Kamigawa',
// 'Lorwyn',
// 'Mirrodin',
// 'Ravnica',
// 'Shandalar',
// 'Zendikar'
// ];
The JSON file is arranged like so:
[
{"id": "Ravnica"},
{"id": "Lorwyn"},
{"id": "Innistrad"},
{"id": "Zendikar"},
{"id": "Kamigawa"},
{"id": "Mirrodin"},
{"id": "Shandalar"},
{"id": "Alara"},
{"id": "Fiora"}
]
Plunker
Any help is much appreciated.
You almost have it, although you are looping through data.id which is not what you want to be doing. You should just loop through data, and push val.id.
If you wanted to loop through data.id, then you're json would have to be structured like so:
{
"id": [
"things",
"to",
"loop",
"through"
]
}
..but it's not, so just loop through data.
Please check following solution. I have hard coded plane data instead of getting from file but solution is same. You just need update your $.each line by iterating over data instead of data.id (this is you'r bug rest of code is fine).
var data = [{
"id": "Ravnica"
}, {
"id": "Lorwyn"
}, {
"id": "Innistrad"
}, {
"id": "Zendikar"
}, {
"id": "Kamigawa"
}, {
"id": "Mirrodin"
}, {
"id": "Shandalar"
}, {
"id": "Alara"
}, {
"id": "Fiora"
}];
var planes = [];
//surround this each with your $.getJSON. I have just hardcoded json data instead of getting it from file
$.each(data, function(index, val) {
planes.push(val.id);
});
console.log(planes);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Your update plunker link Plunker
You might also look into the native array map method, which saves you having to create an array and then push things onto it. It just returns a new array given the original array by applying the mapping function on each item.
$.getJSON("planes.json",function(data){
console.log(data.map(function(plane){return plane.id;}))
}
However, this is not available in IE<=8 if I recall correctly.

D3 - Update chart based on data keys

I am new to D3 and I would like to train myself on some scatterplot chart (based on the one from the NYT : Student debt.
I managed to recreate a graph like this one : Oklahoma Colleges.
Now, I have much more entries than the Oklahoma chart, so my chart is not very readable and I would like to filter the data based on a button with which I can select to display only the "public" colleges or the "private" ones.
I have read many tutorials about the ENTER-UPDATE-EXIT methods but I still have some trouble in applying it practically on my case.
Assuming the following JSON file :
[ {
"Id": 1,
"Name": "College A",
"Type": "Public" }, {
"Id": 2,
"Name": "College B",
"Type": "Private" }, {
"Id": 3,
"Name": "College C",
"Type": "Public" }, {
"Id": 4,
"Name": "College D",
"Type": "Public" }, {
"Id": 5,
"Name": "College E",
"Type": "Private" }, {
"Id": 6,
"Name": "College F",
"Type": "Private" }, ]
I would like to achieve the following algorithm :
button.on("change"){
If value == "public" :
display data where data.type == "public"
Else
display data where data.type == "private"
}
My first solution was to create a SVG each time I push the button (and erase the previous SVG) with the new dataset. But I think there is a much nicer way to do this :)
Can you help me ?
Thank you !
EDIT : following #sapote warrior answer -
Here what I do when I load the data :
d3.json("data.json", function(data) {
//Coerce string to numbers
...
dataset = data;
...
//Add axis and legend
}
And when I click to one of the two button :
function update(input){
var data = [];
for(i in dataset) {
if(dataset[i]["Type"] == input)
data.push(dataset[i]);
}
test = data; //Global scope variable, to check if new data is here on the console
circles = svg.selectAll(".circle")
.data(data);
circles.exit().remove();
circles.enter().append("circle")
.attr("class","circle")
...
}
But this doesn't work perfectly. Circles appear correctly when first click to any button, then they not all disappear when I click to the second button, and the new data doesn't seem to be correctly appended.
Hum, still have some issue understanding the enter-update-exit process ^^
EDIT : Ok problem solved ! I have just made some mistakes when implementing the enter-update-exit methods. Did it with a reduced dataset to understand the issue and now it's clear in my mind :)
I think I may be able to help you. Assuming that your circles are already displayed on the SVG, one way to do it is build a new array of values when your button is clicked that are of type "Public" or "Private". Something like this:
publicButton.on("click", function() {
newData = [];
for(i in existingDataArray) {
if(existingDataArray[i]["Type"] == "Public")
newData.push(existingDataArray[i]);
}
Now you can use this new data with the .data().enter().exit().remove() methods that you mentioned to append new data to your circle objects. After that you can remove those circles that aren't in the new data, and keep those that are. Those that you keep you can then doing something to them like update their color or nothing at all if you like. Sort of like this:
var circles = svg.selectAll("circle").data(newData);
circles.exit().remove();
circles.enter().attr("fill", ...);
}
Hopefully this helps some, let me know if you have any questions.

Nested JSON structure to build side-by-side d3.js charts

I'm currently stuck on how to traverse a JSON structure (I created) in order to create side-by-side donut charts. I think I've created a bad structure, but would appreciate any advice.
I'm working from Mike Bostock's example here: http://bl.ocks.org/mbostock/1305337 which uses a .csv file for source data.
In that example he uses d3.nest() to create a nested data structure with an array of flight origins.
var airports = d3.nest()
.key(function(d) { return d.origin; })
.entries(flights);
He is then able to bind that new data structure to a new div selector:
var svg = d3.select("body").selectAll("div")
.data(airports)
.enter().append("div")
Which allows him to create a donut chart for each flight origin.
My JSON data looks like the following:
{"years": [
{
"year": 2015,
"type": "bestSellers",
"chartOne": [
{
"label": "smarties",
"value": 11
},
{
"label": "nerds",
"value": 13
},
{
"label": "dots",
"value": 16
},
{
"label": "sour patch kids",
"value": 61
}
],
"chartTwo": [
{
"label": "Pepsi",
"value": 36
},
{
"label": "sunkist",
"value": 13
},
{
"label": "coke",
"value": 34
}
]}
I'm a CS student, with little experience of data structure best practices and d3.js. The structure I created doesn't look "flat" to me so I'm not sure if I need to use d3.nest(). However, I'm not clear how to traverse chartOne and chartTwo using the structure as is.
I can get to the arrays within the charts:
var chartOne = years[0].chartOne;
var cartTwo = years[0].chartTwo;
But I would like to be able to have one object to access chart1 and chart2. I'm tempted to create another array block in my JSON, but not clear if there isn't a more simple approach.
No, you don't need to use .nest here. The easiest way to build the required data structure is as you suggest (d3 always wants an array to iterate over):
var nestedData = [ years[0].chartOne, years[0].chartTwo ];
After that, it's as simple as cleaning up the accessor functions for your data and Bostock's example works well.
Example here.

Flatten D3.js Nested Data or Map it to new Dataset

Total noob to D3.js and working on creating my first grouped bar chart. However I'm having trouble making my data fit with the examples online. I am trying to use this example here, with my data that has been nested JSON with D3.
My problem is i cant use the d3.keys method to retrieve keys because my keys are not the state names. They are just Key.
Not to mention the second half forEach wont work because again the keys are not the State names, they are just the term key. So +d[name] will try d[MAPLE] when really my value is inside a key of d.values[(Get the Value where the key = name)]. Just really confused how to do this once the data has been nested in JSON
How would I go about getting all possible Key Values, and then mapping the keys with the next level of keys and values? Using a similar example as below but with my JSON nested data.
var ageNames = d3.keys(data[0]).filter(function(key) { return key !== "State"; });
data.forEach(function(d) {
d.ages = ageNames.map(function(name) { return {name: name, value: +d[name]}; });
});
My data is as so
{
"key": "1/10/2014",
"values": [
{
"key": "Texas",
"values": 200
},
{
"key": "Colorado",
"values": 300
},
{
"key": "Utah",
"values": 227
}
]
},{
"key": "2/10/2014",
"values": [
{
"key": "Texas",
"values": 225
},
{
"key": "Colorado",
"values": 241
},
{
"key": "Utah",
"values": 500
}
]
}
It's not clear from the question if the aim is to group by state ("Texas", "Colorado"...) or date ("1/10/2014", "2/10/2014"...) along the x-axis.
Assuming date (because that's how the data is currently structured), here's a working plunk: http://plnkr.co/edit/C8lkPMGanFY9BkTc6f1i?p=preview
The code that processes the data into a format that your existing D3 code for grouped bar chart can handle looks like this:
// Call the first mapping function on every 'date' in the data array
processed_data = data.map( function (d) {
var y0 = 0;
var total = 0;
return {
date: d.key,
// Call the second mapping function on every 'state' in the given date array
values: (d.values).map( function (d) {
return_object = {
state: d.key,
count: d.values,
y0: y0,
y1: y0 + d.values
};
// Calculate the updated y0 for each new state in a given date
y0 = y0 + d.values;
// Add the total for a given state to the sum total for that date
total = total + d.values;
return return_object;
}),
total: total
};
});
We use nested array.map transforms to manipulate your two-level nested data into the expected format and calculate y0, y1 and total values. Your processed_data object will then look like this:
The only other tricky bit will be to calculate your list of unique states, in order to define the color.domain. If you don't want to hard-code this (e.g. because the list may vary from dataset to dataset, you could use this approach:
// Define the color domain, by first building a list of states:
var states = [];
// Loop through each date
processed_data.forEach(
function (d) {
// Loop through each state in that date
d.values.forEach(
function(d) {
// add to the array if not already present
if (!(states.indexOf(d.state) > -1)) {
states.push(d.state)
}
}
)
}
);
color.domain(states);

Categories