D3: Iterating and accessing datasets? - d3.js

Context
I am making a scatterplot. For each location, in my dataset, I want to plot a circle on a map, using 'lat' and 'long' values. There will be two circles plotted, one on top of the other. The radiuses of the two circles will be defined by 'total' and 'passed' values. I've made my map; I intend my plotted data to look something like this:
I can structure my data any way I want to. I have have opted for json, below.
[
{
"year": 2006,
"inspections": [
{
"location": "Cheshire",
"total": 341,
"passed": 26,
"long": 5,
"lat": 20
},
{
"location": "County Durham",
"total": 102,
"passed": 1,
"long": 480,
"lat": 90
}
]
},
{
"year": 2007,
...
]
Eventually, I'd like to transition my circles (having them grow and shrink) through the years, but for now I'm starting simple and just trying to plot a single year of data on my map.
Here is my first attempt at the code to plot my circles:
d3.json("dataset", function(error, data) {
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
// return someting;
})
.attr("cy", function(d) {
// return someting;
})
.attr("r", 5);
});
Here's the console output of d:
Question
I don't understand too well how .data() and .enter() work? How do I access my values, 'location', 'total', 'passed', 'long' and 'lat', in turn so I can plot all my circles for the year 2006. The examples available tend to use very simple arrays. How do I get my values from my more complex structure of nested arrays and objects?

If your json data is like this
var json = [{
"year": 2006,
"inspections": [{
"location": "Cheshire",
"total": 341,
"passed": 26,
"long": 50,
"lat": 0
}, {
"location": "County Durham",
"total": 102,
"passed": 10,
"long": 52,
"lat": 0
}]
}, {
"year": 2007,
"inspections": [{
"location": "Cheshire",
"total": 341,
"passed": 26,
"long": 51,
"lat": 1
}, {
"location": "County Durham",
"total": 102,
"passed": 10,
"long": 51,
"lat": -1.8
}]
}
];
First make red circle for the first array of inspections like this:
//circle1
svg.selectAll(".red")//get all the circles with class red
.data(json).enter()//iterate over the json
.append("circle")
.attr("cx", function (d) { var circle1 = d.inspections[0]; return projection([circle1.lat,circle1.long])[0]; })//get x point based on projection set the x point which is 0 index
.attr("cy", function (d) { var circle1 = d.inspections[0]; return projection([circle1.lat,circle1.long])[1]; })//get x point based on projection set the x point which is 1 index
.attr("r", "8")
.attr("class", "red")//to get the selection for red circles
.attr("fill", "red")
Make blue circle for the second array of inspections like this:
//circle 2
svg.selectAll(".blue")
.data(json).enter()
.append("circle")
.attr("cx", function (d) { var circle1 = d.inspections[1]; return projection([circle1.lat,circle1.long])[0]; })
.attr("cy", function (d) { var circle1 = d.inspections[1]; return projection([circle1.lat,circle1.long])[1]; })
.attr("r", "8")
.attr("class", "blue")
.attr("fill", "blue")
Working code here
Hope this helps!

Easier to just have a flat array of objects if you can:
[
{
"year": 2006,
"location": "Cheshire",
"total": 341,
"passed": 26,
"long": 5,
"lat": 20
},
{
"year": 2006,
"location": "County Durham",
"total": 102,
"passed": 1,
"long": 480,
"lat": 90
}
]
Then d.lat etc will return what you expect. Otherwise your code looks fine on first glance. Just do your svg.selectAll("circle").data(data).enter().append("circle") twice--once for each set of circles.

Related

Inserting nodes locations for network graph D3

I am new to D3 and I am trying to solve a homework.
I am trying plot a network graph by drawing the nodes from this dataset(sample).
{ "nodes": [
{
"id": "site09",
"x": 317.5,
"y": 282.5
},
{
"id": "site01",
"x": 112,
"y": 47
},
{
"id": "site03",
"x": 69.5,
"y": 287
},
{
"id": "site04",
"x": 424.5,
"y": 99.5
} ]
"links": [
{"node01": "site05", "node02": "site08", "amount": 10},
{"node01": "site05", "node02": "site02", "amount": 120},
{"node01": "site05", "node02": "site03", "amount": 50},
I want to plot the location of the circles using x and y from the dataset.
I tried the following but the circles/nodes are not showing.
//importing the json data
d3.json("project.json", function(data) {
console.log(data);
svgCanvas.selectAll("circle")
.data(data).enter() // create place hodlers if the data are new .append("circle") // create one circle for each
.append("svg:circle")
.attr("cx", function(data){
return data.x;})
.attr("cy", function(data){
return data.y;})
.attr("r", function(thisElement, index){
// use the value from data to create the radius
return thisElement["amount"]})
vis.selectAll("circle.nodes")
.data(nodes)
.enter()
.append("svg:circle")
.attr("class", "nodes")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", "10px")
.attr("fill", "black")
})
}
Thank you,

Transitions with nested data D3.js

I've recently begun learning D3.js and I am struggling to create a transition in a scatter plot with the following data:
var data = [
{"year" : "2004", "x":100, "y":300, "size": 2, "type": "A"},
{"year" : "2005", "x":200, "y":200, "size": 2, "type": "A"},
{"year" : "2006", "x":300, "y":100, "size": 2, "type": "A"},
{"year" : "2004", "x":150, "y":250, "size": 2.382450, "type": "B"},
{"year" : "2005", "x":150, "y":250, "size": 3.078548, "type": "B"},
{"year" : "2006", "x":150, "y":250, "size": 4.265410, "type": "B"}];
Where in the scatter plot there are 2 points (type A&B) and they change location (x&y) and size by year. I've created a fiddle where I try to nest the data and plot the points, but making the next step of using transition() function is confusing. More specifically, I am still declaring the whole data, but to make transitions work I only need part of the data.
The key to understand what you want lies here:
There are 2 points and they change location (x&y) and size by year
Therefore, this is clearly a XY problem. Your problem is not "how to transition with nested data". Your problem is "how to transition by year".
My proposed solution involves, first of all, dropping that nested array. You don't need that.
Instead, get all the years in the data...
var years = [...new Set(data.map(function(d) {
return d.year
}))];
..., filter the data by year...
var dataStart = data.filter(function(d) {
return d.year === years[0]
});
... and loop trough the years. Here, I'm using d3.interval():
var counter = 1;
var timer = d3.interval(transition, 1500);
function transition() {
var newData = data.filter(function(d) {
return d.year === years[counter]
});
svg.selectAll("circle").data(newData)
.transition()
.duration(1000)
.attr("cx", function(d) {
console.log(d)
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("r", function(d) {
return d.size * 10;
});
counter += 1;
if (counter === 3) {
timer.stop()
}
}
Here is the demo:
var data = [{
"year": "2004",
"x": 100,
"y": 100,
"size": 2,
"type": "A"
}, {
"year": "2005",
"x": 200,
"y": 180,
"size": 2,
"type": "A"
}, {
"year": "2006",
"x": 300,
"y": 50,
"size": 2,
"type": "A"
}, {
"year": "2004",
"x": 150,
"y": 150,
"size": 2.382450,
"type": "B"
}, {
"year": "2005",
"x": 150,
"y": 50,
"size": 3.078548,
"type": "B"
}, {
"year": "2006",
"x": 150,
"y": 100,
"size": 4.265410,
"type": "B"
}];
var width = 400,
height = 200;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var years = [...new Set(data.map(function(d) {
return d.year
}))];
var dataStart = data.filter(function(d) {
return d.year === years[0]
});
var cell = svg.selectAll("circle")
.data(dataStart);
cell.enter()
.append("circle")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("r", function(d) {
return d.size * 10;
});
var counter = 1;
var timer = d3.interval(transition, 1500);
function transition() {
var newData = data.filter(function(d) {
return d.year === years[counter]
});
svg.selectAll("circle").data(newData)
.transition()
.duration(1000)
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("r", function(d) {
return d.size * 10;
});
counter += 1;
if (counter === 3) {
timer.stop()
}
}
<script src="https://d3js.org/d3.v4.min.js"></script>

How do i add two different shapes to D3 forced directed graph based on shape field value?

I am newbie to D3. I am using force directed graph. I want to add two different type of shapes at node's places.
My json is following:
{
"nodes":[
{"name":"00:00:00:00:00:00:00:01","group":0,"shape":1},
{"name":"00:00:00:00:00:00:00:02","group":1,"shape":1},
{"name":"00:00:00:00:00:00:00:03","group":2,"shape":1},
{"name":"00:00:00:00:00:00:00:11","group":0,"shape":0},
{"name":"00:00:00:00:00:00:00:21","group":1,"shape":0},
{"name":"00:00:00:00:00:00:00:31","group":2,"shape":0},
{"name":"00:00:00:00:00:00:00:32","group":2,"shape":0},
{"name":"00:00:00:00:00:00:00:12","group":0,"shape":0},
{"name":"00:00:00:00:00:00:00:22","group":1,"shape":0}
],
"links":[
{ "source": 0, "target": 0, "value": 5 },
{ "source": 1, "target": 1, "value": 5 },
{ "source": 2, "target": 2, "value": 5 },
{ "source": 3, "target": 0, "value": 5 },
{ "source": 4, "target": 1, "value": 5 },
{ "source": 5, "target": 2, "value": 5 },
{ "source": 6, "target": 2, "value": 5 },
{ "source": 7, "target": 0, "value": 5 },
{ "source": 8, "target": 1, "value": 5 }
]
}
If shape value is 1, then draw circle, and if shape value is 0, then draw rectangle.
Force directed graph example link is: http://bl.ocks.org/mbostock/4062045
I have tried example link JSFiddle: http://jsfiddle.net/mayurchavda87/Sc2xC/3/
You can do this, as shown in e.g. this example, by using a symbol generator and path elements instead of SVG elements for specific shapes. The code to add shapes becomes
var node = svg.selectAll(".node")
.data(data.nodes)
.enter().append("path")
.attr("class", "node")
.attr("d", d3.svg.symbol()
.type(function(d) { return d3.svg.symbolTypes[d.s]; }))
.style("fill", function(d) { return color(d.group); })
.call(force.drag);
Then you also need to change your tick handler to change the transform attribute of the path elements:
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
Complete jsfiddle here.

Json d3 access each object

{
"name": "Max",
"value": 107,
"children": [
{
"name": "Don",
"value": 60,
"children" [
{"name": "CC", "value": 25},
{"name": "Jim", "value": 35}
]
},
{
"name": "David",
"value": 47,
"children": [
{"name": "Jeff", "value": 32},
{"name": "Buffy", "value": 15}
]
}
]
}
How can I access the inner most child name with d3?
I tried doing :
.text(function (d) { return d.children ? null : d.name; });
But it didn't work....
When I do
.text(function (d) { return d.name });
it only shows the name of the outer loop --> Don, David.
d3.json('flare.json', function (data) {
var canvas = d3.select('p1')
.append('svg')
.attr('width', 800)
.attr('height', 800)
var color = d3.scale.category20c();
var data1 = data.children;
canvas.selectAll('text')
.data(data1)
.enter()
.append('text')
.attr('x', function (d) { return 2; })
.attr('y', function (d, i) { return i * 15; })
.attr('fill', 'black')
.style('font-size', '12px')
.text(function (d) { return d.children ? null: d.name; })
Data I had before ↓ ↓
{
"name": "Don",
"value": 75,
"children" [
{"name": "CC", "value": 25},
{"name": "Jim", "value": 35}
]
}
When the data was in this single nested format, my code worked perfectly, but when I did double nest on it, it no longer works
You need a recursive function for this --
function getNames(d) {
return d.children ? d.children.map(getNames) : d.name;
}
This will return a nested list with the names of the elements that have no children.

Inverting bars on d3.js bar chart

I'm just getting started with D3.js and am having a problem with getting the bars horizontally lined up. Currently they come out pointing downwards.
var jsonRectangles = [
{ "x_axis": 10, "y_axis": 0, "height": 65, "width":20, "color": "green" },
{ "x_axis": 40, "y_axis": 0, "height": 80, "width":20, "color": "purple" },
{ "x_axis": 70, "y_axis": 0, "height": 100, "width":20, "color": "orange" },
{ "x_axis": 100, "y_axis": 0, "height": 50, "width":20, "color": "brown" },
{ "x_axis": 130, "y_axis": 0, "height": 66, "width":20, "color": "black" },
{ "x_axis": 160, "y_axis": 0, "height": 68, "width":20, "color": "red" }];
var svgContainer = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 100);
var rectangles = svgContainer.selectAll("rect")
.data(jsonRectangles)
.enter()
.append("rect");
var rectangleAttributes = rectangles
.attr("x", function (d) { return d.x_axis; })
.attr("y", function (d) { return d.y_axis; })
.attr("height", function(d) { return height - y(d.weight); })
.attr("width", function (d) { return d.width; })
.style("fill", function(d) { return d.color; });
The (0,0) coordinate in an SVG is in the top left corner, so your y coordinates are "reversed" in the sense that they are counted from the top. This means that you have to position your bars so that they start at y position that you want to show and extend to the axis. Your code should look something like this.
rectangles.attr("y", function (d) { return (heightOfGraph - y(d.height)); })
.attr("height", function(d) { return y(d.height); });
On a general note, you don't need to save rectangleAttributes in a variable -- it will be exactly the same as rectangles.
In D3, 0 on the y coordinate is at the top rather than the bottom. You need to fist move the bars down to where you want the y axis origin to be, then move the bars up by their height to position them correctly.
Here's a rough solution though that hopefully you'll be able to work with (see the comments for the bits that have changed):
var jsonRectangles = [
{ "x_axis": 10, "y_axis": 0, "height": 65, "width":20, "color" : "green" },
{ "x_axis": 40, "y_axis": 0, "height": 80, "width":20, "color" : "purple" },
{ "x_axis": 70, "y_axis": 0, "height": 100, "width":20, "color" : "orange" },
{ "x_axis": 100, "y_axis": 0, "height": 50, "width":20, "color" : "brown" },
{ "x_axis": 130, "y_axis": 0, "height": 66, "width":20, "color" : "black" },
{ "x_axis": 160, "y_axis": 0, "height": 68, "width":20, "color" : "red" }];
// height of the visualisation - used to translate the bars
var viz_height = 100;
var svgContainer = d3.select("body").append("svg")
.attr("width", 500)
// set using viz_height rather than a fixed number
.attr("height", viz_height);
var rectangles = svgContainer.selectAll("rect")
.data(jsonRectangles)
.enter()
.append("rect");
var rectangleAttributes = rectangles
.attr("x", function (d) { return d.x_axis; })
// move the bars to the bottom of the chart (using
// viz_height), then move them back up by the height of
// the bar which moves them into palce
.attr("y", function (d) { return viz_height - y(d.height); })
.attr("height", function(d) { return y(d.height); })
.attr("width", function (d) { return d.width; })
.style("fill", function(d) { return d.color; });

Resources