Data structure:
var data = [
{name: "male",
values: [
{ count: 12345,
date: Date 2015-xxx,
name: "male" },
{...}
]
},
{name: "female",
values: [
{ count: 6789,
date: Date 2015-xxx,
name: "female" },
{...}
]
}
]
The values that I want to access are data[a].values[b].count
The values are used to draw circles for my plot
code for circle plot:
focus.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("cx", function(d,i) { return x(d.values[i].date); })
.attr("cy", function(d,i) { return y(d.values[i].count); })
.attr("r", 4)
.style("fill", function(d,i) { return color(d.values[i].name); })
The problem is that i = 1
because of its position in the object.
What I want to do is to loop through all the objects
under values
. How can I do that?
Edit: I wish to learn how to do it without altering the data, to improve on my skills.
Thanks.
There are several ways to do what you want using D3 only, without any other library and without altering the data. One of them is using groups
to deal with "higher" levels of data (regarding the nested data). Let's see it in this code:
First, I mocked up a dataset just like yours:
var data = [
{name: "male",
values: [{ x: 123,y: 234},
{ x: 432,y: 221},
{ x: 199,y: 56}]
},
{name: "female",
values: [{ x: 223,y: 111},
{ x: 67,y: 288},
{ x: 19, y: 387}]
}
];
This is the data we're gonna use. I'm gonna make a scatter plot here (just as an example), so, let's set the domains for the scales accessing the second level of data (x
and y
inside values
):
var xScale = d3.scaleLinear().range([20, 380])
.domain([0, d3.max(data, function(d){
return d3.max(d.values, function(d){
return d.x;
})
})]);
var yScale = d3.scaleLinear().range([20, 380])
.domain([0, d3.max(data, function(d){
return d3.max(d.values, function(d){
return d.y;
})
})]);
Now comes the most important part: we're gonna bind the data to "groups", not to the circle elements:
var circlesGroups = svg.selectAll(".circlesGroups")
.data(data)
.enter()
.append("g")
.attr("fill", function(d){ return (d.name == "male") ? "blue" : "red"});
Once in the first level of data we have 2 objects, D3 will create 2 groups for us.
I also used the groups to set the colours of the circles: if name
is "male", the circle is blue, otherwise it's red:
.attr("fill", function(d){ return (d.name == "male") ? "blue" : "red"});
Now, with the groups created, we create circles according to the values
in the data of each group, binding the data as follows:
var circles = circlesGroups.selectAll(".circles")
.data(function(d){ return d.values})
.enter()
.append("circle");
Here, function(d){ return d.values}
will bind the data to the circles according to the objects inside values
arrays.
And then you position your circles. This is the whole code, click "run code snippet" to see it:
var data = [
{name: "male",
values: [{ x: 123,y: 234},
{ x: 432,y: 221},
{ x: 199,y: 56}]
},
{name: "female",
values: [{ x: 223,y: 111},
{ x: 67,y: 288},
{ x: 19, y: 387}]
}
];
var xScale = d3.scaleLinear().range([20, 380])
.domain([0, d3.max(data, function(d){
return d3.max(d.values, function(d){
return d.x;
})
})]);
var yScale = d3.scaleLinear().range([20, 380])
.domain([0, d3.max(data, function(d){
return d3.max(d.values, function(d){
return d.y;
})
})]);
var xAxis = d3.axisBottom(xScale).tickSizeInner(-360);
var yAxis = d3.axisLeft(yScale).tickSizeInner(-360);
var svg = d3.select("body")
.append("svg")
.attr("width", 400)
.attr("height", 400);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0,380)")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(20,0)")
.call(yAxis);
var circlesGroups = svg.selectAll(".circlesGroups")
.data(data)
.enter()
.append("g")
.attr("fill", function(d){ return (d.name == "male") ? "blue" : "red"});
var circles = circlesGroups.selectAll(".circles")
.data(function(d){ return d.values})
.enter()
.append("circle");
circles.attr("r", 10)
.attr("cx", function(d){ return xScale(d.x)})
.attr("cy", function(d){ return yScale(d.y)});
.axis path, line{
stroke: gainsboro;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
The easiest way is to use a lib like underscore.js to edit your data array.
From underscore docs:
flatten _.flatten(array, [shallow]) Flattens a nested array (the nesting can be to any depth). If you pass shallow, >the array will only be flattened a single level.
_.flatten([1, [2], [3, [[4]]]]); -> [1, 2, 3, 4]; _.flatten([1, [2], [3, [[4]]]], true); -> [1, 2, 3, [[4]]];
map _.map(list, iteratee, [context]) Alias: collect Produces a new array of values by mapping each value in list through a >transformation function (iteratee). The iteratee is passed three arguments: the >value, then the index (or key) of the iteration, and finally a reference to the >entire list.
_.map([1, 2, 3], function(num){ return num * 3; }); => [3, 6, 9] _.map({one: 1, two: 2, three: 3}, function(num, key){ return num * 3; }); => [3, 6, 9] _.map([[1, 2], [3, 4]], _.first); => [1, 3]
Underscore documentation
In your code you can do something like that:
var flatData = _.flatten(_.map(data, (d)=>d.values));
focus.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("cx", function(d,i) { return x(d.date); })
.attr("cy", function(d,i) { return y(d.count); })
.attr("r", 4)
.style("fill", function(d,i) { return color(d.name); })
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With