Reputation: 1151
I created the following color scale:
Here's the code:
function continuous(selector_id, colorscale) {
var legendheight = 200,
legendwidth = 80,
margin = { top: 10, right: 60, bottom: 10, left: 2 };
var canvas = d3
.select(selector_id)
.style("height", legendheight + "px")
.style("width", legendwidth + "px")
.style("position", "relative")
.append("canvas")
.attr("height", legendheight - margin.top - margin.bottom)
.attr("width", 1)
.style("height", legendheight - margin.top - margin.bottom + "px")
.style("width", legendwidth - margin.left - margin.right + "px")
.style("border", "1px solid #000")
.style("position", "absolute")
.style("top", margin.top + "px")
.style("left", margin.left + "px")
.node();
var ctx = canvas.getContext("2d");
var legendscale = d3
.scaleLinear()
.range([1, legendheight - margin.top - margin.bottom])
.domain(colorscale.domain());
var image = ctx.createImageData(1, legendheight);
d3.range(legendheight).forEach(function (i) {
var c = d3.rgb(colorscale(legendscale.invert(i)));
image.data[4 * i] = c.r;
image.data[4 * i + 1] = c.g;
image.data[4 * i + 2] = c.b;
image.data[4 * i + 3] = 255;
});
ctx.putImageData(image, 0, 0);
var legendaxis = d3.axisRight().scale(legendscale).tickSize(6).ticks(8);
var svg = d3
.select(selector_id)
.append("svg")
.attr("height", legendheight + "px")
.attr("width", legendwidth + "px")
.style("position", "absolute")
.style("left", "0px")
.style("top", "0px");
svg
.append("g")
.attr("class", "axis")
.attr(
"transform",
"translate(" +
(legendwidth - margin.left - margin.right + 3) +
"," +
margin.top +
")"
)
.call(legendaxis);
}
useEffect(() => {
var colorScale = d3.scaleSequential(d3.interpolateRdYlGn).domain([-4, 16]);
var svg = continuous("#legend1", colorScale);
}, []);
Since my data is skewed towards values above 0, I want to create a custom scale with skewed red-to-green coloring and a transition occurring at 0
rather than 6
. However, I'm having trouble implementing custom scaling functionality.
I followed suggestions from this post but was unable to implement the functionality, namely:
var custom = d3.scaleLinear()
.domain([0, 50, 90, 95, 100])
.range(['#edfc1b','#ec6f3b','#bc2e67','#7c0093', '#0b0074']);
At the moment, all I'm getting from the code above is:
So, my question: how can I get the full domain plotted if I have an array of values such as [0, 50, 90, 95, 100]
?
Alternatively, is there a better way to create a custom color scale in D3?
Upvotes: 1
Views: 641
Reputation: 1931
The code that defines the scale is correct:
var custom = d3.scaleLinear()
.domain([0, 50, 90, 95, 100])
.range(['#edfc1b','#ec6f3b','#bc2e67','#7c0093', '#0b0074']);
It is the code that translate this color scale to a spatial scale that is the issue:
var legendscale = d3
.scaleLinear()
.range([1, legendheight - margin.top - margin.bottom])
.domain(colorscale.domain())
The code above creates a scale with a range of length 2, and a domain of length 5. The scaleLinear
needs to arrays of equal lengths in the domain and range, otherwise it will not do a 1-to-1 map as expected.
A quick fix when the length of the domain is fixed (in your case, 5) is to manually expand the range with the intermediate steps.
var max = legendheight - margin.top - margin.bottom;
var legendscale = d3
.scaleLinear()
.range([1, max / 4, 2*(max / 4), 3*(max / 4), max])
.domain(colorscale.domain())
Upvotes: 1