I am trying to create a choropleth map with d3.js(v6). I am able to show the map however no data is encoded. I am having trouble grouping the data by row. My csv file looks like this:
code, location, date, cases
AFG, Afghanistan,2010, 10000
AFG, Afghanistan,2011, 30000
AFG, Afghanistan,2012, 60000
ALB, Albania,2010, 1000
ALB, Albania,2011, 3000
ALB, Albania,2012, 6000
ALG, ALgeria,2010, 4000
ALG, ALgeria,2011, 7000
ALG, ALgeria,2012, 16000...
I am trying to group the rows so I would get one value for each location using the following code but it doesn't seem to work.
Promise.all([
d3.json("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/world.geojson"),
d3.csv("data.csv", function(d) {
let datagroup = d3.group(data, d => d.location);
datagroup.set(d.location, +d.cases)
})]).then(function(loadData){
let topo = loadData[0]
// Draw the map
svg1.append("g")
.selectAll("path")
.data(topo.features)
.enter()
.append("path")
// draw each country
.attr("d", d3.geoPath()
.projection(projection)
)
// set the color of each country
.attr("fill", function (d) {
d.total = data.get(d.id) || 0;
return colorScale(d.total);
})
I would appreciate it if someone could take a look and point out my mistake. Thanks a lot!
Related
I am using j3.ds to deploy some data on a pie chart. It seems to work fine and it updates correctly when I introduce new data. The thing is, I wanted to do the transition when updating smoothly, like here:
https://www.d3-graph-gallery.com/graph/pie_changeData.html
For some reason it is not working when I introduce the merge and transition, can somebody help with the task? thanks in advance
update();
function update() {
var data = d3.selectAll('.values').nodes();
var pie = d3.pie() //we create this variable, for the values to be readeable in the console
.value(function(d) {return d.innerHTML; })(data);
console.log("pie = ",pie)
var u = svg.selectAll("path")
.data(pie)
console.log("u = ",u)
// Build the pie chart: Basically, each part of the pie is a path that we build using the arc function
u
.enter()
.append('path')
.merge(u)
.transition()
.duration(2000)
.attr('d', d3.arc()
.innerRadius(0)
.outerRadius(radius)
)
.attr('fill', function(d,i){ return color[i] })
.attr("stroke", "black")
.style("stroke-width", "2px")
.style("opacity", 1)
}
Merge combines one selection with another as follows:
selectionCombined = selection1.merge(selection2);
You aren't providing a second selection, so you aren't merging anything. The selection you do have you are calling .merge() on is the enter selection, returned by .enter() - unless you add new slices to the pie, this will be empty every update after the first. As you aren't merging anything with the enter selection, post merge the selection is still empty.
The enter selection is used to create elements so that for every item in the data array there is one corresponding element in the DOM - as you already have slices, only the update selection is not empty.
The update selection is that returned by .data(), it contains existing elements which correspond to items in the data array. You want to merge this selection with the one returned by .enter():
var update = svg.selectAll("path")
.data(pie)
var enter = update.enter()
.append('path')
var merged = update.merge(enter)
However, a transition needs a start value and an end value. In your case you are trasitioning the d attribute of a path. On update the start value is the path's current d and the end value is a d representing the new value for the slice. On initial entry, what is the value that the transition should start from? It may be more appropriate to only transition on update:
var arc = d3.arc().innerRadius(0).outerRadius(radius);
var update = svg.selectAll("path")
.data(pie)
var enter = update.enter()
.append('path')
// Set initial value:
.attr('d', arc)
// If these values don't change, set only on enter:
.attr('fill', function(d,i){ return color[i] })
.attr("stroke", "black")
.style("stroke-width", "2px")
update.transition()
// transition existing slices to reflect new data.
.attr('d',arc)
Note: transitioning paths can be difficult - you'll notice the deformity to the pie in the transition in your example. This is because of how the d attribute string is interpolated. If you want to preserve the radius a different approach is needed in applying the transition.
I've made a plunker that updates data from one csv file to another, the yaxis updates accordingly but the rectangles don't.
The .attr("height", function(d) { return Math.abs(y(d[0])) - y(d[1]); }); portion of the code still has the old data from the previous file (I'm guessing).
I'm guessing this is because I haven't declared .data(series) in the updateData() function, I remember doing something like this in another chart
g.selectAll(".bar").data(series).transition()
etc...
but this doesn't work in this chart.
I can't figure it out, any help is appreciated!
The problem was that you didn't join the new data to existing bars.
To make this work well, you will want to specify a key for category of data when you join the series to the g elements to ensure consistency (although I notice that category-1 is positive in the first dataset, and negative in the second, but this is test data i guess)
Here's the updated plunkr (https://plnkr.co/edit/EoEvVWiTji7y5V3SQTKJ?p=info), with the relevant code highlighted below:
g.append("g")
.selectAll("g")
.data(series, function(d){ return d.key }) //add function to assign a key
.enter().append("g")
.attr("class", "bars") //so its easy to select later on
//etc
...
function updateData() {
d3.csv("data2.csv", type, function(error, data) {
///etc
let bars = d3.selectAll(".bars") //select the g elements
bars.data(series, function(d){ return d.key }) //join the new data
.selectAll(".bar")
.data(function(d) { return d; })
.transition()
.duration(750)
.attr("y", function(d) { return y(d[1]); })
.attr("height", function(d) { return Math.abs(y(d[0])) - y(d[1]); });
I have build a connection by using d3. The codes show the data and method of connection:
var places = {
TYO: [139.76, 35.68],
BKK: [100.48, 13.75],
BER: [13.40, 52.52],
NYC: [-74.00, 40.71],
};
var connections = {
CONN1: [places.TYO, places.BKK],
CONN2: [places.BER, places.NYC],
};
...
svg.append("path")
.datum({type: "LineString", coordinates: connections.CONN1})
.attr("class", "route")
.attr("d", path);
svg.append("path")
.datum({type: "LineString", coordinates: connections.CONN2})
.attr("class", "route")
.attr("d", path);
You can see my codes, that I use the two identical methods to build two connections. That is not good to build more connections.
I am wondering, if there is a loop function to interpret the connections by using data "connections" directly? I mean, I could get information for data "connections" and use them directly to build connections.
I have thought some ways, such as .datum({type: "LineString", function(d,i) {
return coordinates: connections[i];});. But it does not work.
Could you please tell me some way to solve it? Thanks.
Generally when you want to append many features in d3, you want to use an array not an object. With an array you can use a d3 enter selection which will then allow you to build as many features as you need (if sticking to an object, note that connections[0] is not what you are looking for, connections["conn1"] is).
Instead, use a data structure like:
var connections = [
[places.TYO, places.NYC],
[places.BKK, places.BER],
...
]
If you must have identifying or other properties for each datapoint use something like:
var connections = [
{points:[places.TYO, places.NYC],id: 1,...},
{points:[places.BKK, places.BER],id: 2,...},
...
]
For these set ups you can build your lines as follows:
paths = svg.selectAll(".connection")
.data(connections)
.enter()
.append("path")
.attr("class","connection")
.attr('d', function(d) {
return path ({
type:"LineString",
coordinates: d
});
})
See here. Or:
paths = svg.selectAll(".connection")
.data(connections)
.enter()
.append("path")
.attr("class","connection")
.attr('d', function(d) {
return path ({
type:"LineString",
coordinates: d.points
});
})
Alternatively, you can use a data set up like:
var connections = [
{target:"TYO", source:"NYC"},
{target:"BKK", source: "BER"},
...
]
paths = svg.selectAll(".connection")
.data(connections)
.enter()
.append("path")
.attr("class","connection")
.attr('d', function(d) {
return path ({
type:"LineString",
coordinates: [ places[d.source],places[d.target] ]
});
})
See here.
If selecting elements that don't yet exist, using these lines
d3.select("...")
.data(data)
.enter()
.append("path")
will append a path for each item in the data array - this means that d3 generally avoids the use of for loops as the desired behavior is baked right into d3 itself.
Working with two charts in D3. I have a pie chat displaying parent data regarding a budget. When the user mouses over a pie slice, I am trying to push that slice's array data to a bar chart.
My data is setup like so:
{"Department":"Judiciary",
"Funds1415":317432,
"Fundsb":"317.4",
"annual": [ 282,288,307,276,276,298,309,317,317 ]
},
I'm trying to use this to pass the annual array to the barchart:
path.on('mouseover', function(d) {
...
bars.selectAll('rect').transition().attr("y", function(d) { return h - d.data.annual /125; });
bars.selectAll('rect').transition().attr("height", function(d) { return d.data.annual / 125; });
});
And here's the barchart I'm trying to send it to:
var bars = svg.selectAll("rect")
.data(budget)
.enter()
.append("rect")
.attr("class", "barchart")
.attr("transform", "translate(26,109)")
.attr("fill", function(d, i) {
return color2(i);
})
.attr('class', 'barchart')
.attr("x", function(d, i) {
return i * 14;
})
.attr("width", 12)
.attr("y", 100)
.attr("height", 100);
Link to full code here:
http://jsbin.com/zayopecuto/1/edit?html,js,output
Everything 'seems' to be working, except the data either isn't passing or it isn't updating the bar chart.
I've been banging my head up against this for a couple of days, to no avail. Originally I was thinking of placing the annual data in separate arrays and just transitioning from data source to data source on mouseover, but that seems backward and unnecessary.
First, your selector is wrong. bars is already a collection of rects, so you can't re-select the rects. Second, you haven't bound "updated" data to those rects. So, with this in mind, it becomes:
bars
.data(d.data.annual)
.transition()
.attr("height", function(d) {
return d / 125;
})
.attr("y", function(d) {
return h - d /125;
});
Here's an updated example.
What I understand from your code and comment is that, you have data points for your donut chart and each data object contains a property called 'annual' which you want to use as a input data for the bar chart.
You should be calling a separate function to plot your bar chart passing it the annual data array.
Clear the existing bar chart on 'mouseout' event, so that a new bar chart can be plotted on the next 'mouseover' event. You can use jQuery empty() function for clearing out the chart container.
I have a d3.js problem and have struggled with this for a while and just can not seem to solve it. I believe it is pretty easy, but am missing something very basic.
Specifically, I have the following code, which generates a line and 2 circles for the 1st entry in the JSON - I have 'hardcoded' it for the first entry.
I'd like to now add the 2nd and 3rd entries of the JSON file to the graph and have control over line and circle colors and then generalize the code.
From reading the documentation and StackOverflow, it seems like the proper approach is to use nesting, but I can't seem to make it work?
The code is on jsfiddle at the following URL and the javascript is below.
http://jsfiddle.net/GVmVk/
// INPUT
dataset2 =
[
{
movie : "test",
results :
[
{ week: "20130101", revenue: "60"},
{ week: "20130201", revenue: "80"}
]
},
{
movie : "beta",
results :
[
{ week: "20130101", revenue: "40"},
{ week: "20130201", revenue: "50"}
]
},
{
movie : "gamm",
results :
[
{ week: "20130101", revenue: "10"},
{ week: "20130201", revenue: "20"}
]
}
];
console.log("1");
var parseDate = d3.time.format("%Y%m%d").parse;
var lineFunction = d3.svg.line()
.x(function(d) { return xScale(parseDate(String(d.week))); })
.y(function(d) { return yScale(d.revenue); })
.interpolate("linear");
console.log("2");
//SVG Width and height
var w = 750;
var h = 250;
//X SCALE AND AXIS STUFF
//var xMin = 0;
//var xMax = 1000;
var xScale = d3.time.scale()
.domain([parseDate("20130101"),parseDate("20131231")])
.range([0, w]);
console.log(parseDate("20130101"));
console.log("3");
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom");
console.log("4S");
//Y SCALE AND AXIS STUFF
var yScale = d3.scale.linear()
.domain([0, 100])
.range([h, 0]);
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(5);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
console.log("4S1");
//CREATE X-AXIS
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (h - 30) + ")")
.call(xAxis);
//Create Y axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + 25 + ",0)")
.call(yAxis);
svg.selectAll("circle")
.data(dataset2[0].results)
.enter()
.append("circle")
.attr("cx", function(d) {
// console.log(d[0]);
console.log(parseDate(d.week));
return xScale(parseDate(d.week));
})
.attr("cy", function (d) {
return yScale(d.revenue);
})
.attr("r", 3);
//create line
var lineGraph = svg.append("path")
.attr("d", lineFunction(dataset2[0].results))
.attr("class", "line");
The word "nesting" comes up in two contexts in d3 -- creating nested data arrays with d3.nest, and using nested data to create nested selections.
Your data is already in the correct format for a nested selection -- an array of objects, each of which has a sub-array of individual data points. So you don't need to worry about manipulating the data, you just need to go straight to joining your data to your elements in nested d3 selections:
I'm going to take you through it quickly, but the following tutorials will be good reference for the future:
Thinking with Joins
Nested Selections
How Selections Work
On to your example: you have a top-level data structure that is an array of movie objects, each of which contains a sub-array of weekly revenue values. The first thing you need to decide is what type of elements you want associated with each level of data. You're drawing a line and a set of circles for the data in the sub-array, but aren't currently adding anything for the top-level array objects (the movies). You need to add something for them in order for nested selections to work, and it needs to be something that can contain your line and circle. In SVG, that's almost always going to be a <g> (grouping) element.
To efficiently create one <g> element for every object in your data array -- and to attach the data objects to the elements for future reference -- you create an empty selection, join your data to it, then use the enter() method of the data join selection to add elements for each data object that didn't match an element. In this case, since we don't have any elements to start, all the data objects will be in the enter() selection. However, the same pattern also works when updating some of the data.
var movies = svg //start with your svg selection,
//it will become the parent to the entering <g> elements
.selectAll("g.movie") //select all <g> elements with class "movie"
//that are children of the <svg> element
//contained in the `svg` selection
//this selection will currently be empty
.data( dataset2 ); //join the selection to a data array
//each object in the array will be associated with
//an element in the selection, if those elements exist
//This data-joined selection is now saved as `movies`
movies.enter() //create a selection for the data objects that didn't match elements
.append("g") //add a new <g> element for each data object
.attr("class", "movie") //set it's class to match our selection criteria
//for each movie group, we're going to add *one* line (<path> element),
//and then a create subselection for the circles
.append("path") //add a <path> within *each* new movie <g> element
//the path will *inherit* the data from the <g> element
.attr("class", "line"); //set the class for your CSS
var lineGraph = movies.select("path.line")
//All the entered elements are now available within the movies selection
//(along with any existing elements that we were updating).
//Using select("path") selects the first (and only) path within the group
//regardless of whether we just created it or are updating it.
.attr("d", function(d){ return lineFunction(d.results); });
//the "d" attribute of a path describes its shape;
//the lineFunction creates a "d" definition based on a data array.
//If the data object attached to the path had *only* been the d.results array
//we could have just done .attr("d", lineFunction), since d3
//automatically passes the data object to any function given as the value
//of an .attr(name, value) command. Instead, we needed to create an
//anonymous function to accept the data object and extract the sub-array.
var circles = movies.selectAll("circle")
//there will be multiple circles for each movie group, so we need a
//sub-selection, created with `.selectAll`.
//again, this selection will initially be empty.
.data( function(d) {return d.results; });
//for the circles, we define the data as a function
//The function will be called once for each *movie* element,
//and passed the movie element's data object.
//The resulting array will be assigned to circles within that particular
//movie element (or to an `enter()` selection, if the circles don't exist).
circles.enter() //get the data objects that don't have matching <circle> elements
.append("circle") //create a circle for each
//the circles will be added to the appropriate "g.movie"
//because of the nested selection structure
.attr("r", 3); //the radius doesn't depend on the data,
//so you can set it here, when the circle is created,
//the same as you would set a class.
circles //for attributes that depend on the data, they are set on the entire
//selection (including updating elements), after having created the
//newly entered circles.
.attr("cx", function(d) { return xScale( parseDate(d.week) ); })
.attr("cy", function(d) { return yScale( d.revenue ); });
Live version with the rest of your code: http://jsfiddle.net/GVmVk/3/
You'll need to adjust the domain of your x-scale so that the first data points aren't cut off, and you'll need to decide how you want to use your movie title property, but that should get you going.
Yes indeed, nested selection are the way to go for the circles, although you don't need them for the paths:
svg.selectAll("g.circle")
.data(dataset2)
.enter()
.append("g")
.attr("class", "circle")
.selectAll("circle")
.data(function(d) { return d.results; })
.enter()
.append("circle")
.attr("cx", function(d) {
// console.log(d[0]);
console.log(parseDate(d.week));
return xScale(parseDate(d.week));
})
.attr("cy", function (d) {
return yScale(d.revenue);
})
.attr("r", 3);
//create line
var lineGraph = svg.selectAll("path.line")
.data(dataset2).enter().append("path")
.attr("d", function(d) { return lineFunction(d.results); })
.attr("class", "line");
Complete example here.