I'm trying to create a cartogram using cartogram.js and d3.js. I've used the examples found in the cartogram.js repo and here to put together a script that generates a world map inside an SVG using the d3.geo.mercator() projection and now I'm trying to distort the map using the cartogram.js library however I'm getting the following error:
d3.js:8756 Error: <path> attribute d: Expected number, "MNaN,NaNLNaN,NaNL…".
(anonymous function) # d3.js:8756
tick # d3.js:8956
(anonymous function) # d3.js:8936
d3_timer_mark # d3.js:2166
d3_timer_step # d3.js:2147
Here's my code I'm using to distort the map:
var dataLoaded = new Event("dataLoaded"),
svg = d3.select("svg"),
proj = d3.geo.mercator(),
path = d3.geo.path()
.projection(proj),
countries = svg.append("g")
.attr("id", "countries")
.selectAll("path"),
carto = d3.cartogram()
.projection(proj)
.properties(function(d) {
return d.properties
}),
mapData = d3.map(),
geometries,
topology
function init() {
d3.csv("data/data.csv", function(data) {
data.forEach(function (d) {
mapData.set(d.COUNTRY, d.VALUE)
})
})
d3.json("data/world.json", function(data) {
topology = data
geometries = topology.objects.countries
var features = carto.features(topology, geometries)
countries = countries
.data(features)
.enter()
.append("path")
.attr("fill", function (e) {
return "#000000"
})
.attr("d", path)
document.dispatchEvent(dataLoaded)
})
}
document.addEventListener("dataLoaded", function() {
$("#container").css("visibility", "visible").hide().fadeIn("fast")
$("header").css("visibility", "visible").hide().fadeIn("slow")
carto.value(function(d) {
return +mapData.get(d.properties.name)
})
countries.data(carto(topology, geometries).features)
countries.transition()
.duration(750)
.attr("d", carto.path);
})
init()
and the CSV file containing the data I want to use to distort the map:
COUNTRY,VALUE
Afghanistan,90
Albania,390
Algeria,90
Andora,110
Angola,10
Antigua,2400
Argentina,320
Armenia,40
Australia,6600
Austria,1300
Axerbaijan,0
Bahamas,1900
Bahrain,90
Bangladesh,50
Barbados,8100
Belarus,20
Belgium,260
Belize,480
Benin,0
Bhutan,170
Bolivia,90
Bosnia,70
Botswana,110
Brazil,1300
Brunei,40
Bulgaria,3600
Burkina Faso,0
Burundi,0
Cabo Verde,0
Cambodia,720
Cameroon,10
Canada,4400
Central African Republic,0
Chad,10
Chile,320
China,1600
Combodia,0
Comoros,10
Congo,20
Costa Rica,2900
Cote d'Ivoire,0
Croatia,9900
Cuba,14800
Cyprus,8100
Czech Republic,70
Denmark,320
Dijbouti,0
Dominica,0
Dominican Republic,4400
Ecuador,90
Egypt,6600
El Salvador,10
Equatorial Guinea,0
Eritrea,10
Estonia,110
Ethiopia,70
Fiji,1900
Finland,720
France,2900
Gabon,10
Gambia,2400
Georgia,70
Germany,880
Ghana,210
Greece,14800
Grenada,720
Guatemala,40
Guinea,0
Guinea - Bissau,0
Guyana,50
Haiti,90
Honduras,110
Hungary,170
Iceland,8100
India,2900
Indonesia,390
Iran,390
Iraq,140
Ireland,1900
Israel,590
Italy,9900
Jamaica,6600
Japan,3600
Jordan,480
Kazakhstan,40
Kenya,1000
Kiribati,10
Kosovo,10
Kuwait,40
Kyrgyzstan,10
Laos,70
Latvia,110
Lebanon,70
Lesotho,0
Liberia,10
Libya,30
Liechtenstein,10
Lithuania,70
Luxembourg,50
Macedonia,70
Madagascar,0
Malawi,40
Malaysia,1300
Maldives,12100
Mali,40
Malta,12100
Marshall Islands,10
Mauritania,10
Mauritius,6600
Mexico,18100
Micronesia,20
Moldova,20
Monaco,590
Mongolia,110
Montenegro,880
Morocco,4400
Mozambique,90
Myanmar,90
Namibia,210
Nauru,10
Nepal,0
Netherlands,50
New Zealand,1900
Nicaragua,50
Niger,10
Nigeria,90
North Korea,390
Norway,1600
Oman,590
Pakistan,110
Palau,50
Palestine,10
Panama,210
Papua New Guinea,40
Paraguay,10
Peru,1000
Philippines,590
Poland,880
Portugal,12100
Qatar,210
Romania,320
Russia,480
Rwanda,20
Saint Kitts and Nevis,0
Saint Lucia,90
Saint Vincent and the Grenadines,0
Samoa,90
San Marino,70
Sao Tome and Principe,10
Saudi Arabia,110
Senegal,70
Serbia,50
Seychelles,1600
Sierra Leone,20
Singapore,880
Slovakia,70
Slovenia,390
Solomon Islands,10
Somalia,70
South Africa,1900
South Korea,140
South Sudan ,0
Spain,14800
Sri Lanka,3600
Sudan,20
Suriname,10
Sweden,720
Switzerland,1300
Syria,590
Taiwan,50
Tajikistan,10
Tanzania,260
Thailand,14800
Timor-Leste,0
Togo,10
Tonga,50
Trinidad and Tobago,140
Tunisia,4400
Turkey,9900
Turkmenistan,10
Tuvalu,30
Uganda,50
Ukraine,70
United Arab Emirates,20
United Kingdom,50
United States of America,3600
Uruguay,50
Uzbekistan,30
Vanuatu,30
Vatican City,30
Venezuela,170
Vietnam,2400
Yemen,20
Zambia,90
Zimbabwe,70
I don't have any experience using d3.js prior to this project so I would appreciate any feedback/guidance you can give me.
I'm using version 3.5.17 of d3, fyi.
Thanks.
UPDATE - 9/8/2016 15:22 BST
As per #Mark's suggestion, I've implemented d3-queue, although the problem still persists. If I've done anything wrong with this implementation, however, I'd be grateful for any insight anyone can give me! :)
var svg = d3.select("svg"),
proj = d3.geo.mercator(),
path = d3.geo.path()
.projection(proj),
countries = svg.append("g")
.attr("id", "countries")
.selectAll("path"),
carto = d3.cartogram()
.projection(proj)
.properties(function(d) {
return d.properties
}),
queue = d3.queue()
.defer(csv)
.defer(json)
.awaitAll(ready),
mapData = d3.map(),
geometries,
topology
function json(callback) {
d3.json("data/world.json", function(data) {
topology = data
geometries = topology.objects.countries
var features = carto.features(topology, geometries)
countries = countries
.data(features)
.enter()
.append("path")
.attr("fill", function (e) {
return "#000000"
})
.attr("d", path)
callback()
})
}
function csv(callback) {
d3.csv("data/data.csv", function(data) {
data.forEach(function (d) {
mapData.set(d.COUNTRY, +d.VALUE)
})
callback()
})
}
function ready() {
$("#container").css("visibility", "visible").hide().fadeIn("fast")
$("header").css("visibility", "visible").hide().fadeIn("slow")
carto.value(function(d) {
if (mapData.has(d.properties.name)) {
return +mapData.get(d.properties.name)
}
})
countries.data(carto(topology, geometries).features)
countries.transition()
.duration(750)
.attr("d", carto.path);
}
UPDATE 2 - 9/8/2016 18:05 BST
Here is the latest version of the script on Plunker which can be used for testing, courtesy of #Mark: http://plnkr.co/edit/iK9EZSIfwIXjIEBHhlep?p=preview
It seems my initial error has been fixed although the resulting cartogram isn't displaying correctly.
UPDATE 3 - 10/8/2016 20:45 BST
#Mark's answer helped clarify a lot of my issues and I had a partially functioning cartogram as a result however to fix the issue detailed here, I regenerated my map file using the --stitch-poles false parameter and and after doing this I am once again receiving the following error:
d3.js:8756 Error: <path> attribute d: Expected number, "MNaN,NaNLNaN,NaNL…".
#Mark's initial fix for this error is still in place therefore I'm quite confused as to why this has resurfaced. You can see my latest code here and my new map topojson file here. Thanks again.
Okay, I'm making progress. It turns out that after fixing your .value function the reason you don't get a catrogram is the your values are too disparate. Why this throws off cartogram.js, I'm unsure, but the problem can be easily solved by introducing a scale.
With your data:
s = d3.scale.linear().range([1,100]).domain(d3.extent(data, function(d){ return +d.VALUE}));
And then in your .value accessor:
carto.value(function(d,i) {
if (mapData.has(d.properties.name)) {
return s(mapData.get(d.properties.name));
} else {
return 1;
}
});
Alas, though, all your problems aren't fixed. It seems that countries that "wrap" the projection (ie Russia and Fiji) get distorted by the paths generated by cartogram.js. Here's a fix though, discussed at length here
Regardless of that, here's what we've got so far.
I've setup an example to demonstrate the issue's I've encountered:
To be brief, I'm using d3 to render a map of the united states. I'm appending relevant data attributes to handle click events and identify which state was clicked.
On click events the following is preformed:
I'm grabbing the US County topojson file (which contains ALL US
Counties).
Removing irrelevant counties from the data and rendering them on the
map of the state that was clicked.
I can't seem to figure out what is going on behind the scenes that is causing some of the counties to be drawn while others are ignored.
When I log the data that is returned from the filtered list, I'm displaying the accurate number of counties, but they are only partially drawn on the map. Some states don't return any. Pennsylvania and Texas partially work.
I've checked the data and the comparison operations, but I'm thinking this may have to do with arcs properties being mismatched.
If I utilize the Counties JSON file to render the entire map of the united states they are all present.
If anyone can help shed some light on what might be happening that would be great.
svg {
fill:#cccccc;
height:100%;
width:100%;
}
.subunit{
outline:#000000;
stroke:#FFFFFF;
stroke-width: 1px;
}
.subunit:hover{
fill:#ffffff;
stroke:#FFFFFF;
stroke-width="10";
}
<body>
<script src="http://www.cleanandgreenfuels.org/jquery-1.11.3.min.js"></script>
<script src="http://www.cleanandgreenfuels.org/d3.v3.min.js"></script>
<script src="http://www.cleanandgreenfuels.org/topojson.v1.min.js"></script>
<script>
var width = window.innerWidth;
height = window.innerHeight;
var projection = d3.geo.albers()
.scale(1500)
.translate([width / 2, height / 2]);
//d3.geo.transverseMercator()
//.rotate([77 + 45 / 60, -39 - 20 / 60]);
//.rotate([77+45/60,-40-10/60])
//.scale(500)
//.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("body").append("svg")
.attr("width", width+"px")
.attr("height", height+"px");
d3.json("http://www.cleanandgreenfuels.org/usstates2.json.php", function(error, us, init) {
//svg.append("path")
// .datum(topojson.feature(us, us.objects.counties))
//.attr("d", path);
function init(){
$( document ).ready(function() {
$('.subunit').on('click',function(){
var stateid = $(this).attr("data-stateid");
function clearStates(stateid){
d3.json("http://www.cleanandgreenfuels.org/uscounties2.json.php", function(error, us) {
console.log(us);
console.log("DATA CLICKED ID "+stateid);
test = jQuery.grep(us.objects.counties.geometries, function(n){
return (n.properties.stateid == stateid);
});
us.objects.counties.geometries = test;
console.log(test.length);
console.log(us.objects.counties.geometries.length);
var test = topojson.feature(us, us.objects.counties).features;
console.log(test);
console.log(test.length);
svg.selectAll(".subunit")
.data(test)
.enter().append("path")
.attr("class", function(d) { return "subunit"; })
.attr("d", path)
.attr("data-countyid", function(r){ return r.id; });
});
}
clearStates(stateid);
});
});
}
svg.selectAll(".subunit")
.data(topojson.feature(us, us.objects.us).features)
.enter().append("path")
.attr("class", function(d) { return "subunit"; })
.attr("d", path)
.attr("data-stateid", function(r){ return r.id; });
init();
});
</script>
</body>
It appears as if I was attempting to utilize some outdated features, using topojson.mesh and .datum() to add the new data has resolved this issue, but has introduced a new error.
Now it appears as if the polygons that are rendered must be in sequence to be drawn properly this way.
I think the data going in should be cleaned up to optimize the way d3 is designed to function, but I'd still like to know more about how it is rendering this information that is obtained from the dataset.
function clearStates(stateid){
d3.json("http://www.cleanandgreenfuels.org/uscounties2.json.php", function(error, us) {
console.log(us);
console.log("DATA CLICKED ID "+stateid);
test = jQuery.grep(us.objects.counties.geometries, function(n){
return (n.properties.stateid == stateid);
});
us.objects.counties.geometries = test;
console.log(test.length);
console.log(us.objects.counties.geometries.length);
**var test = topojson.mesh(us, us.objects.counties);**
console.log(test);
console.log(test.length);
**svg.append("path")
.datum(test)
.attr("class", function(d) { return "subunit"; })
.attr("d", path)
.attr("data-countyid", function(r){ return r.id; });**
});
}