Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

svg

d3.js

So i'm trying to make a map from an .svg file I produced with Illustrator because it's a map of the Netherlands with not so straightforward regions.

All the regions have their own #ID.

Now i'm trying to color each region according to their value in the dataset. I can force color on the regions by CSS ive done so on one region but thats obviously not good solution.

If I for example try to select(#id) and then change the .attr("fill","red"); it doesnt work.

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

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

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

like image 833
user2123511 Avatar asked Mar 10 '14 15:03

user2123511


People also ask

How to fill color in SVG image?

Edit your SVG file, add fill="currentColor" to svg tag and make sure to remove any other fill property from the file. Note that currentColor is a keyword (not a fixed color in use). After that, you can change the color using CSS, by setting the color property of the element or from it's parent.

Can we group SVG elements in d3js?

The <g> SVG element is a container used to group other SVG elements. Transformations applied to the <g> element are performed on its child elements, and its attributes are inherited by its children. We can create a group element with D3. js by appending a g element using any selection.

Which tag is used for marking points in D3 using SVG?

An SVG line element is represented by <line> tag. A line's attributes are: x1: This is the x-coordinate of the first point. y1: This is the y-coordinate of the first point.


1 Answers

The problem is that your Illustrator file already specifies fill colours on the individual <path> elements, and your id values are for parent <g> elements. Child elements inherit styles from parents, but only if the child doesn't have values of its own.

There are a couple things you could do to change it:

  1. Change the Illustrator file so that the paths have no fill. Then they will inherit a fill colour set on the parent.

  2. Select the paths directly, using d3.selectAll("g#id path") or d3.select("g#id").selectAll("path"); either version will select all <path> elements that are descendents of the <g> elment with id "id". Then you can set the fill attribute directly to over-write the value from Illustrator.


As discussed in the comments to the main question, if you want to take this a step further and actually join the data to the elements for future reference (e.g., in an event handler), the easiest way is to loop through your dataset, select each element, then use the .datum(newData) method to attach the 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 be able to select all the top-level <g> elements in the future, I would suggest also giving them a class, so you can select them with, 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
  });

Example implementation: http://jsfiddle.net/ybAj5/7/

Although using dataset.forEach doesn't seem to be using the full capability of d3, it is actually much simpler than trying to attach the whole dataset at once -- especially since there is such variability in the structure of the 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's data if it exists, else the grandparent's 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 fiddle shows the above code in action, but again I would recommend using the forEach approach.

like image 134
AmeliaBR Avatar answered Oct 15 '22 21:10

AmeliaBR