How to update fill color on existing svg elements using d3.js?

So, I am trying to create a map from a .svg file created using Illustrator, because it is a map of the Netherlands with not so simple areas.

All regions have their #ID.

Now I am trying to color each region according to their value in the dataset. I can force color across regions using CSS, done this way in one place, but this is clearly not a good solution.

If, for example, I try to select (#id), then change .attr ("fill", "red"); he does not work.

How to update region colors by id using d3.js according to d [1] value in the data set?

Files: https://gist.github.com/gordonhatusupy/9466794

Direct link: http://www.gordonjakob.me/regio_map/

+8
svg
source share
1 answer

The problem is that your Illustrator file already points to fill colors on individual <path> elements, and your id values ​​for parent <g> elements. Child elements inherit styles from parents, but only if the child does not have its own values.

There are several things you could do to change it:

  • Modify the Illustrator file so that the paths do not fill. Then they inherit the fill color specified by the parent.

  • Select paths directly using d3.selectAll("g#id path") or d3.select("g#id").selectAll("path") ; any version will select all <path> elements that are descendants of <g> elment with id id. You can then set the fill attribute directly to overwrite the value from Illustrator.


As discussed in the comments on the main question, if you want to take this step further and actually attach the data to the elements for future reference (for example, in the event handler), the easiest way is to set the data, select each element, then use the .datum(newData) to attach data to each element:

 dataset.forEach(function(d){ //d is of form [id,value] d3.select("g#"+d[0]) //select the group matching the id .datum(d) //attach this data for future reference .selectAll("path, polygon") //grab the shapes .datum(d) //attach the data directly to *each* shape for future reference .attr("fill", colour(d[1]) ); //colour based on the data }); 

http://jsfiddle.net/ybAj5/6/

If you want to select all the top-level <g> elements in the future, I would also suggest giving them a class, so you can select them, for example, d3.select("g.region") . For example:

 dataset.forEach(function(d){ //d is of form [id,value] d3.select("g#"+d[0]) //select the group matching the id .datum(d) //attach this data for future reference .classed("region", true) //add a class, without erasing any existing classes .selectAll("path, polygon") //grab the shapes .datum(d) //attach the data directly to *each* shape for future reference .attr("fill", colour(d[1]) ); //colour based on the data }); d3.selectAll("g.region") .on("click", function(d,i) { infoBox.html("<strong>" + d[0] + ": </strong>" + d[1] ); //print the associated data to the page }); 

Implementation example: http://jsfiddle.net/ybAj5/7/

Although using dataset.forEach does not seem to use the full d3 feature, it’s actually much easier than trying to attach the entire data set at once - especially since there is such variability in the structure of regions, some of which have nested <g> elements :

 //Option two: select all elements at once and create a datajoin d3.selectAll("g[id]") //select only g elements that have id values .datum(function(){ var id=d3.select(this).attr("id"); return [id, null]; }) //create an initial [id, value] dataset based on the id attribute, //with null value for now .data(dataset, function(d){return d[0];}) //use the first entry in [id,value] as the key //to match the dataset with the placeholder data we just created for each .selectAll("path, polygon") //grab the shapes .datum(function(){ return d3.select(this.parentNode).datum() || d3.select(this.parentNode.parentNode).datum(); }) //use the parent data if it exists, else the grandparent data .attr("fill", function(d){return d?colour(d[1]):"lightgray";}); //set the colour based on the data, if there is a valid data element //else use gray. 

This script shows the above code in action, but I would recommend using the forEach approach.

+11
source share

All Articles