whiterook6
whiterook6

Reputation: 3534

How to create a custom color scale?

I am trying to map a series of version strings to a series of RGB colors in hex form. Given a fixed set of version strings (eg ["1.1.2", "1.1.3", ...]) and a couple of colors (["#efebeb", "#4d4d4d", "#ff5d5d"]) I want to create a scale that gives me an interpolated color for each version string.

I tried this:

const palette = scaleOrdinal().domain(props.sdkVersions).range(["#efebeb", "#4d4d4d", "#ff5d5d"]);

But this just alternates between the three colors:

ordinal scale just rotates through set colors.

I'm looking for smooth interpolation. When I try other scales typescript complains that it's the wrong types, or that they need to be numbers, etc. I'd rather not precalculate the list of colors since I won't know how many I'll need.

Is this possible?

Upvotes: 0

Views: 1871

Answers (1)

Gerardo Furtado
Gerardo Furtado

Reputation: 102174

Because you have a fixed set of colours that you want to interpolate, no matter how big is the domain length, my solution involves using a linear scale to create the colours array, which you'll pass to the ordinal scale.

Therefore, you can create the linear scale with the 3 colours you want...

const linearScale = d3.scaleLinear()
  .domain([0, 0.5, 1])
  .range(["#efebeb", "#4d4d4d", "#ff5d5d"]);

... and then you populate the colours array, which you'll pass to the ordinal scale:

const colorArray = d3.range(data.length)
    .map(d => linearScale(d / (data.length - 1)));

const ordinalScale = d3.scaleOrdinal()
  .domain(data)
  .range(colorArray);

The advantage of this approach is that it's dynamic, working with any number of elements in the domain.

Here is a demo with just 3 elements (that is, in your case, three versions):

const linearScale = d3.scaleLinear()
	.domain([0, 0.5, 1])
  .range(["#efebeb", "#4d4d4d", "#ff5d5d"]);
  
const data = "abc".split("");

const colorArray = d3.range(data.length).map(d=>linearScale(d/(data.length - 1)));

const ordinalScale = d3.scaleOrdinal()
	.domain(data)
  .range(colorArray);
  
const divs = d3.select("body")
	.selectAll(null)
  .data(data)
  .enter()
  .append("div")
  .style("background-color", d=>ordinalScale(d))
div {
  display: inline-block;
  width: 18px;
  height: 18px;
  margin-right: 4px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

Now with 10 versions:

const linearScale = d3.scaleLinear()
	.domain([0, 0.5, 1])
  .range(["#efebeb", "#4d4d4d", "#ff5d5d"]);
  
const data = "abcdefghij".split("");

const colorArray = d3.range(data.length).map(d=>linearScale(d/(data.length - 1)));

const ordinalScale = d3.scaleOrdinal()
	.domain(data)
  .range(colorArray);
  
const divs = d3.select("body")
	.selectAll(null)
  .data(data)
  .enter()
  .append("div")
  .style("background-color", d=>ordinalScale(d))
div {
  display: inline-block;
  width: 18px;
  height: 18px;
  margin-right: 4px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

And, finally, with 50 versions:

const linearScale = d3.scaleLinear()
	.domain([0, 0.5, 1])
  .range(["#efebeb", "#4d4d4d", "#ff5d5d"]);
  
const data = d3.range(50);

const colorArray = d3.range(data.length).map(d=>linearScale(d/(data.length - 1)));

const ordinalScale = d3.scaleOrdinal()
	.domain(data)
  .range(colorArray);
  
const divs = d3.select("body")
	.selectAll(null)
  .data(data)
  .enter()
  .append("div")
  .style("background-color", d=>ordinalScale(d))
div {
  display: inline-block;
  width: 18px;
  height: 18px;
  margin-right: 4px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

Upvotes: 3

Related Questions