four-eyes
four-eyes

Reputation: 12394

Dynamically create legend in d3

I am using d3.scale.quantize() and the colorBrewer to get a colorscale.

....
var extent = d3.extent(collection.features, function(d) {
        return d.properties.mean;
    });

var colorScale = d3.scale.quantize()
    .domain(extent)
    .range(colorbrewer.RdYlBu[8]);
...

That gives me 8 different colors, corresponding to certain ranges of given values. I then use coloScale to fill the svg

....
.attr("fill-opacity", 0.1)
.attr("stroke", "grey")
.style("fill", function(d) {
    return colorScale(d.properties.mean);
});
...

How do I know which range of values corresponds to the color "#fdae61" or "#fee090"? How would I access these values? I want to make a legend that changes dynamically when I change the number of input colors, i.e. from 8 to 3 as well as the color used in the colorscale, i.e. from RdYlBu to YlGn.

I think I am very close. I have this

var scale = d3.scale.ordinal()
    .domain()
    .range(colorScale.range());

I only need to fill the domain with the corresponding values which I then can use to easily create my legend... However, I do not know how to extract them dynamically. Something like colorScale.invertExtent("#fdae61") is to static since I would need to change the colors etc. whenever I change my colorscale.

Upvotes: 2

Views: 1555

Answers (2)

Drenai
Drenai

Reputation: 12357

You could set any dynamic properties on the data objects as well. Then, access the data object values to retrieve these properties.

Something like:

.style("fill", function(d) {
    var color = colorScale(d.properties.mean);
    // put the generated color on your data object 
    d.properties.myFillColor = color;
    return color
});

Then it's a matter of retrieving this myFillColor from the underlying data objects wherever you need it e.g. in the legend

Upvotes: 0

Gerardo Furtado
Gerardo Furtado

Reputation: 102194

If you want to create an automatic legend, my suggestion is that you create a dataset based on your colorScale domain and range, and bind this dataset to your legend. This way, the dataset changes whenever you change the domain or the range of you scale.

For instance, if you have this scale, with the domain going from 0 to 500:

var colorScale = d3.scale.quantize()
    .domain([0, 500])
    .range(["#d73027", "#f46d43", "#fdae61", "#fee090", "#e0f3f8",
           "#abd9e9", "#74add1", "#4575b4"]);//this is colorBrewer.RdYlBu[8]

You could create an array that has all the ranges of values. This will be our dataset, named colorRange:

var colorRange = [];    
for(var i = 0; i < colorScale.range().length; i++){
    colorRange.push(colorScale.invertExtent(colorScale.range()[i])[0]);
};

Based on the previous code, if we console.log this array, we get:

console.log(colorRange);// returns [0, 62.5, 125, 187.5, 250, 312.5, 375, 437.5]

Which contains the corresponding domain values for your 8 colors. If we for instance remove two colors from colorScale range, we have now:

console.log(colorRange);// returns [0, 83.333, 166.666, 250, 333.333, 416.666]

Once you have this colorRange array, you not only have the domain values for your legend, but you can easily set the colors as well, using:

colorScale(colorRange[i])

Where i goes from the first value to the last.

PS: If you were using a quantile scale instead, we could drop the cumbersome for loop and simply use [0].concat(colorScale.quantiles()) to create our array.

Upvotes: 2

Related Questions