Referencing json data in D3.js - javascript

Basically I'm doing a basic enough family tree where each person is represented by a with their own properties. One property is "a Marriage" tag with can have attributes such as "married", "single" etc. but "widowed" is dependant on whether their spouse is dead or not. I decided to work off "id_number" as the marriage reference as different people can have the same name.
Assuming I have json data which looks like this:
var people = [
{ "name": "John Doe", "id_number": 1, "date_of_death": "1984", "married": 2, "father": 0, "mother": 0},
{ "name": "Jane Doe", "id_number": 2, "date_of_death": "1994", "married": 1, "father": 0, "mother": 0}
];
var person = d3.select("body").append("div")
.data(people)
.enter()
.append("div")
.text(function (d) { return d.name; })
.attr("id", function (d) {
if (typeof(d.married) != "undefined"){
if (typeof(people[married].date_of_death) != "undefined"){ ## error occurs here
m_status = "married";
} else {
m_status = "widowed";
}
}else{
m_status = "single";
}
return m_status;
});
My question is what is the proper syntax to reference a separate line of data in the same json file?
I have a feeling nodes solves the issue but I have yet to get any example node code working locally even without any changes...

Related

Iterating through a single row of data

My data happens to be in the form:
data = { "year" : 2001,
"category 1": 1234,
"category 2": 2345,
"category 3": 3456,
...
}
And so I'm going to be making a graphic with these lines of code:
svg.selectAll("left.coord")
.data(data)
.enter()
.append("circle")
.attr("r", 3)
.attr("cy", function(d, i){
return d[i]; //or whatever
})
.attr("cx", 0)
Basically, I need the function inside cy to return 1234, then 2345, then 3456, etc. I know how to do this if my data was an array of objects, but what do I do in this case? The code doesn't throw any errors, but it doesn't do anything either.

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 to filter JavaScript object based on property condition?

I am working on a d3 visualization to create a sunburst. I am also adding a functionality to search for a particular arc so that the sunburst displays arcs with only the searched label. If the initial object is:
{
"name": "root",
"children": [
{
"name": "A",
"color": "red",
"children": [
{
"name": "B",
"color": "red"
}
]
},
{
"name": "C",
"color": "red",
"children": [
{
"name": "D",
"color": "red"
}
]
},
{
"name": "E",
"color": "red",
"children": [
{
"name": "B",
"color": "red"
}
]
}
]
}
I would like to filter this such that it returns the whole hierarchy which contains the searched name anywhere in the hierarchy. Below is a sample output required when searched for "name":"B"
{
"name": "root",
"children": [
{
"name": "A",
"color": "red",
"children": [
{
"name": "B",
"color": "red"
}
]
},
{
"name": "E",
"color": "red",
"children": [
{
"name": "B",
"color": "red"
}
]
}
]
}
Please tell me if anything else might be needed. Thank you.
Code snippet of how I tried to filter.
var path = svg.selectAll("path")
.data(partition.nodes(root))
.enter()
.append("path")
.filter(function(d){
return (d.name=="B");
})
.attr("d", arc)
.style("fill", function(d) {
console.log(d.name);
return color((d.children ? d : d.parent).name);
})
This returns only "name":"B" arc and not the hierarchy.
If you want the .filter() way to work for you, you will have to flatten your JSON data apply a filter and then unflatten it. You can check out flattening and unflattening if you are interested.
I personally prefer the depth-first-search (or bfs also works) and pruning where required approach. Lets assume your json data is present in mydata javascript var:
d3.json("jsonData.json",function(error,jsonData){
if(error)
return console.warn(error);
var mydata = jsonData;
dfs(mydata,'B');
});
Now your dfs() function will look something like this:
function dfs(data,label){
if(!('children' in data))
return data.name==label;
for(var i=0;i<data.children.length;++i)
if(dfs(data.children[i],label)==flase)
data.children.splice(i--,1);
return data.children.length > 0 || data.name==label;
}
What is happening here is that we are doing a depth-first search of the JSON data. The first IF condition checks if we are at the leaf node level and returns false if the 'name' property is anything but 'B'. Next we traverse all children of the current node, call dfs() for each child and prune that child if it returns false. At the end we return false if all children of current node have been pruned and if the 'name' property of current node is not 'B' (the condition that the non-leaf node also has to be pruned from the hierarchy). Hope this helps. Let me know if I missed something.
My hunch is that you want to color the searching node and its parent in sunburst.
To do this i have made a function which recursively colors a node's parent like this:
function colorGraph(node){
var sel = svg.selectAll("path").filter(function (d) {
return (node==d);
});
sel.style("opacity", 1);
//if parent present change its opacity
if(node.parent){
colorGraph(node.parent)
}
}
Full working code here
You will need to pre-filter, rather than doing it within the D3 pipeline.
Define a filter function we will call hasName which checks if a particular object has the desired name, or, recursively, if any of its children have it:
// Does the object contain a specified name, however many levels down?
function hasName(obj, name) {
return function _hasName(o) {
return o.name === name || (o.children || []).some(_hasName);
}(obj);
}
Now filter:
data.children = data.children.filter(function(child) { return hasName(child, 'B'); });

jSon to D3js grouping data from nested arrays

I'm very new to doing anything with d3 and jSon. Here is a pice of data I'm trying to get out from json and I would just like to know if I'm even on the right path.
Basically each status group would have more servers inside than just one like at the moment and the idea would be to get rectangle graph for one server and list these nicely next to each other.
I've been reading a lot of tutorials and trying to browse for similiar kind of issues other people might've had, but so far had really no luck...
jSon data I'm trying to pull out
[
{
"status": "ok",
"servers":
[
{
"id": "VR01",
"servername": "Server_1",
"cpu": 45, "mem": 25,
"diskIO": 0, "bandwith": 200
}
]
},
{
"status": "attention",
"servers":
[
{
"id": "VR10",
"servername": "Server_10",
"cpu": 55, "mem": 35,
"diskIO": 1, "bandwith": 2000
}
]
},
{
"status": "warning",
"servers":
[
{
"id": "VR02",
"servername": "Server_02",
"cpu": 98, "mem": 85,
"diskIO": 1,
"bandwith": 2000
}
]
},
{
"status": "dead",
"servers":
[
{
"id": "VR20",
"servername": "Server_20",
"cpu": 0, "mem": 0,
"diskIO": 0,
"bandwith": 0
}
]
}
]
the D3 bit
<script>
var width = ("width", 1000);
var height = ("height", 800);
d3.json("mydata.json", function(data) {
var canvas = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
var status = function sortData(data){
for (i = 0; i < d.length; i++) {
if(d.status ==="ok")
canvas.selectAll("rect")
.data(d.server)
.enter()
.append("rect")
.attr("x", 25)
.attr("y", function(d, i){return 25 * i;})
.attr("fill", "purple")
}
}
})
</script>
Really appreciate any suggestions you might have!
I think that it would be better to use nested selections to create your dashboard.
// Create one group for each server group
var serverGroup = svg.selectAll('g')
.data(data)
.enter()
.append('g')
.attr('transform', function(d, i) { return 'translate(0, ' + 50 * i + ')');
// Create the inner elements for each group
var servers = serverGroup.selectAll('rect')
.data(function(d) { return d.servers; })
.enter()
.append('rect')
// ... more settings here ...
This will create three groups, one for each group of servers and translate each one vertically. Each group contains the group data, so we can use the group data to create elements inside each group. Also, you can add a title, background color and other settings for each group using this structure. This article contains the concepts that you need to work on your problem: How Selections Work. Regards,

Can I update DOM elements in a D3 selector based on a data join to a different data object?

tl;dr
Can I create elements using data that looks like:
[{"id": 1, ...}, {"id: 2, ...}, ..]
and update attributes of the elements using data that looks like:
[2, 4, 7]
(where the elements of the array are a subset of the ids of the initial data set).
Long version
I have data that looks like this:
[
{
"id": 1,
"latitude": 38.314552,
"longitude": -88.9025347755102,
"name": "JEFFERSON COUNTY JAIL ",
"official_name": "Jefferson County Justice Center",
"url": "https://www.ice.gov/detention-facilities/facilities/jeffeil.htm"
},
{
"id": 2,
"latitude": 41.875702,
"longitude": -87.63072,
"name": "CHICAGO HOLD ROOM ",
"official_name": "Chicago Hold Room",
"url": ""
},
{
"id": 3,
"latitude": 43.407029,
"longitude": -88.704349,
"name": "DODGE COUNTY JAIL, JUNEAU ",
"official_name": "Dodge Detention Facility",
"url": "https://www.ice.gov/detention-facilities/facilities/dodgewi.htm"
},
...
]
I put it on an SVG map like this:
var projection = d3.geo.albersUsa()
.scale(scale)
.translate([width / 2, height / 2]);
var facilityCircles = svg.append("svg:g")
.attr("id", "facilities");
d3.json("data/facilities.json", function(facilities) {
var positions = [];
var positionsByFacilityId = {};
var pointSize = 5;
facilities.forEach(function(facility) {
var loc = [facility.longitude, facility.latitude];
var pos = projection(loc);
positions.push(pos);
positionsByFacilityId[facility.id] = pos;
});
facilityCircles.selectAll("circle")
.data(facilities, function(d) { return d.id; })
.enter().append("svg:circle")
.attr("data-facility-id", function(d, i) { return d.id; })
.attr("cx", function(d, i) { return positionsByFacilityId[d.id][0]; })
.attr("cy", function(d, i) { return positionsByFacilityId[d.id][1]; })
.attr("r", function(d, i) { return pointSize; });
}
However, later on, I want to update attributes of the circles based on another data object, which would just be an array of ids representing a subset of the id properties from the initial data, e.g. [2, 4, 5]
Can I do something like this to update the attributes of only selected elements mapped to data objects with the given ids?
facilitiesSubset = [2, 4, 5];
facilityCircles.selectAll("circle")
.data(facilitiesSubset)
.attr("fill", "green");
or, should I just extract the ids from the initial data and use those in the call to data() that is used to create the elements?
To make this a little easier, I'd suggest changing the naming convention a little bit:
var facilityCircleContainer = svg.append("svg:g")
.attr("id", "facilities");
Since the svg:g object isn't actually the circles, it is just holding them. So then:
var facilityCircles = facilityCircleContainer.selectAll("circle")
.data(facilities, function(d) { return d.id; })
.enter().append("svg:circle")
.attr("data-facility-id", function(d, i) { return d.id; })
.ect(...)
facilityCircles now refers to the circles with their attached data now. Updating the fill based on the array is pretty simple:
facilityCircles.attr("fill", function(d){
facilitiesSubset.indexOf(d.id) != -1 ? "green" : "red"});

Categories