Reputation: 63
I'm trying to make a choropleth map in d3 js.
In my code I'm using gejson to draw french departments (counties) and then I want to color them using data from a csv file.
First I populate every counties with their official ID which is a number of 5 digits (ex : 75001).
Then I want to color them using a colorScale. To do so, I do a for each loop where I select counties using their ID (in the csv file this time) and I use the color scale and the data form the csv to get the color of the countie on the map.
I think that the problem problem is d3.select("#d" + e.insee) doesn't work.
I have taken the problem in every way possible and I really can"t figure out what is wrong in the code.
Here is my entire code. The data are loaded from a github so every one can execute it. I apologized for the long code.
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr">
<head>
<meta charset="utf-8">
<title>All in One</title>
<script src="https://d3js.org/d3.v5.min.js"></script>
<style type="text/css">
#info {
margin-top: 50px;
}
#deptinfo {
margin-top: 30px;
}
.department {
cursor: pointer;
stroke: black;
stroke-width: .5px;
}
.department:hover {
stroke-width: 2px;
}
div.tooltip {
position: absolute;
opacity:0.8;
z-index:1000;
text-align:left;
border-radius:4px;
-moz-border-radius:4px;
-webkit-border-radius:4px;
padding:8px;
color:#fff;
background-color:#000;
font: 12px sans-serif;
max-width: 300px;
height: 40px;
}
#svg {
display: block;
margin: auto;
}
</style>
</head>
<body>
<div id="map"></div>
</body>
</html>
<script type="text/javascript">
const width = 850,
const height = 800,
colors = ['#d4eac7', '#c6e3b5', '#b7dda2', '#a9d68f', '#9bcf7d', '#8cc86a', '#7ec157', '#77be4e', '#70ba45', '#65a83e', '#599537', '#4e8230', '#437029', '#385d22', '#2d4a1c', '#223815'];
const path = d3.geoPath();
const projection = d3.geoMercator()
.center([2.332978, 48.860117])
.scale(40000)
.translate([width / 2, height / 2]);
path.projection(projection);
const svg = d3.select('#map').append("svg")
.attr("id", "svg")
.attr("width", width)
.attr("height", height)
.attr("class", "Blues");
// Append the group that will contain our paths
const deps = svg.append("g");
var promises = [];
promises.push(d3.json('https://raw.githubusercontent.com/cerezamo/dataviz/master/Graphique_bokeh/pop_comgeo.geojson'))
promises.push(d3.csv("https://raw.githubusercontent.com/cerezamo/dataviz/master/variables.csv"))
Promise.all(promises).then(function(values){
const geojson = values[0];
const csv = values[1];
var features = deps
.selectAll("path")
.data(geojson.features)
.enter()
.append("path")
.attr('id', function(d) {return "d" + d.properties.insee;})// Creation of the id as (ex :"d75005")
// I add a d so the id is not a pure number as it could create error when selecting it
.attr("d", path);
var quantile = d3.scaleQuantile()
.domain([0, d3.max(csv, function(e) { return + e.densitehabkm2; })])
.range(colors);
var legend = svg.append('g')
.attr('transform', 'translate(725, 150)')
.attr('id', 'legend');
legend.selectAll()
.data(d3.range(colors.length))
.enter().append('svg:rect')
.attr('height', '20px')
.attr('width', '20px')
.attr('x', 5)
.attr('y', function(d) { return d * 20; })
.style("fill", function(d) { return colors[d]; });
var legendScale = d3.scaleLinear()
.domain([0, d3.max(csv, function(e) { return +e.densitehabkm2; })])
.range([0, colors.length * 20]);
var legendAxis = svg.append("g")
.attr('transform', 'translate(750, 150)')
.call(d3.axisRight(legendScale).ticks(3));
csv.forEach(function(e,i) {
d3.select(("#d" + e.insee)) // Line where I think the problem is
// Here I'm trying to select Id's using the same code but reading it in the csv file. I have check and id's in geojson and csv do correspond
.style("fill", function(d) { return quantile(+e.densitehabkm2); })
.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div.html("IZI")
.style("left", (d3.event.pageX + 30) + "px")
.style("top", (d3.event.pageY - 30) + "px");
})
.on("mouseout", function(d) {
div.style("opacity", 0);
div.html("")
.style("left", "-500px")
.style("top", "-500px");
});
});
//console.log(csv.insee);
});
// Append a DIV for the tooltip
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
</script>
Thank you very much for your time.
Upvotes: 1
Views: 402
Reputation: 102174
SVG <path>
elements have, indeed, a black fill by default. However, that's not a problem here: your style("fill", ...)
should work, regardless.
The problem here is that you have several paths with the same ID. If you have a look at the original GeoJson, you'll see that you have several insee
properties, for different years. So, your code is painting several black paths, one on top of the other. When you select by ID you select only one of them (by the way, it goes without saying that IDs should be unique in the document), and the other black paths avoid you seeing the painted path. Also, your solution simply makes all paths transparent, so when you paint any one of them it will be visible, but all other transparent paths are still there, over the path you selected.
All that being said, the simplest solution is filtering the original data, for instance:
geojson.features = geojson.features.filter(function(d) {
return d.properties.year === 1962;
});
Basically, by selecting just one year, you avoid all those paths stacked one on top of the other (and also the browser will render the page way faster).
With that change alone, your style
method will work. Here is the running demo:
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr">
<head>
<meta charset="utf-8">
<title>All in One</title>
<script src="https://d3js.org/d3.v5.min.js"></script>
<style type="text/css">
#info {
margin-top: 50px;
}
#deptinfo {
margin-top: 30px;
}
.department {
cursor: pointer;
stroke: black;
stroke-width: .5px;
}
.department:hover {
stroke-width: 2px;
}
div.tooltip {
position: absolute;
opacity: 0.8;
z-index: 1000;
text-align: left;
border-radius: 4px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
padding: 8px;
color: #fff;
background-color: #000;
font: 12px sans-serif;
max-width: 300px;
height: 40px;
}
#svg {
display: block;
margin: auto;
}
</style>
</head>
<body>
<div id="map"></div>
</body>
</html>
<script type="text/javascript">
const width = 850,
height = 800,
colors = ['#d4eac7', '#c6e3b5', '#b7dda2', '#a9d68f', '#9bcf7d', '#8cc86a', '#7ec157', '#77be4e', '#70ba45', '#65a83e', '#599537', '#4e8230', '#437029', '#385d22', '#2d4a1c', '#223815'];
const path = d3.geoPath();
const projection = d3.geoMercator()
.center([2.332978, 48.860117])
.scale(40000)
.translate([width / 2, height / 2]);
path.projection(projection);
const svg = d3.select('#map').append("svg")
.attr("id", "svg")
.attr("width", width)
.attr("height", height)
.attr("class", "Blues");
// Append the group that will contain our paths
const deps = svg.append("g");
var promises = [];
promises.push(d3.json('https://raw.githubusercontent.com/cerezamo/dataviz/master/Graphique_bokeh/pop_comgeo.geojson'))
promises.push(d3.csv("https://raw.githubusercontent.com/cerezamo/dataviz/master/variables.csv"))
Promise.all(promises).then(function(values) {
const geojson = values[0];
const csv = values[1];
geojson.features = geojson.features.filter(function(d) {
return d.properties.year === 1962;
})
var features = deps
.selectAll("path")
.data(geojson.features)
.enter()
.append("path")
.attr('id', function(d) {
return "d" + d.properties.insee;
}) // Creation of the id as (ex :"d75005")
// I add a d so the id is not a pure number as it could create error when selecting it
.attr("d", path);
var quantile = d3.scaleQuantile()
.domain([0, d3.max(csv, function(e) {
return +e.densitehabkm2;
})])
.range(colors);
var legend = svg.append('g')
.attr('transform', 'translate(725, 150)')
.attr('id', 'legend');
legend.selectAll()
.data(d3.range(colors.length))
.enter().append('svg:rect')
.attr('height', '20px')
.attr('width', '20px')
.attr('x', 5)
.attr('y', function(d) {
return d * 20;
})
.style("fill", function(d) {
return colors[d];
});
var legendScale = d3.scaleLinear()
.domain([0, d3.max(csv, function(e) {
return +e.densitehabkm2;
})])
.range([0, colors.length * 20]);
var legendAxis = svg.append("g")
.attr('transform', 'translate(750, 150)')
.call(d3.axisRight(legendScale).ticks(3));
csv.forEach(function(e, i) {
d3.select(("path#d" + e.insee)) // Line where I think the problem is
// Here I'm trying to select Id's using the same code but reading it in the csv file. I have check and id's in geojson and csv do correspond
.style("fill", function() {
return quantile(+e.densitehabkm2);
})
.on("mouseover", function(d) {
console.log(d);
div.transition()
.duration(200)
.style("opacity", .9);
div.html("IZI")
.style("left", (d3.event.pageX + 30) + "px")
.style("top", (d3.event.pageY - 30) + "px");
})
.on("mouseout", function(d) {
div.style("opacity", 0);
div.html("")
.style("left", "-500px")
.style("top", "-500px");
});
});
//console.log(csv.insee);
});
// Append a DIV for the tooltip
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
</script>
Upvotes: 2
Reputation: 63
Well the problem was in fact in the css.
Thanks to Ryan Morton for the answer.
Add
.attr('fill', 'none')
to when you first create the map objects. It's autofilling with black and somehow preventing your colors later: https://jsfiddle.net/bz3o5yah/
Upvotes: -1