Having a tough time getting my colors to correspond with cell data in my heatmap

Link to my project:

project on replit

My problem:
So far, this is as close as I can get to different colors appearing in different threads, but they are going “in order” of color rather than in order of the temperature data.

I know this is in part related to the fact that I put year data in the scaleThreshold rather than temperature data

// make legendScale - helps to determine color of cell
  let temperatures = data.map(d => d.temperature)
  let legendThreshold = d3
    .scaleThreshold()
    .domain(ticks)
    .range(['#13315C', '#134074', '#06AED5', '#FFE156', '#E8C547', '#FAA916', '#FF4B1A', '#FF3A24', '#FF1F2E', '#DF2935']);

But if I try to use the temperature data (or any other data), my heat map becomes just one color.

I found a S.O. question that suggests it is because the arrays used in my domain and range do not have a length that are 1 apart from each other (i.e. length of 13 and 14).

If I test that out, like so…

  let temperatures = data.map(d => d.temperature)
  let legendThreshold = d3
    .scaleThreshold()
    .domain([1, 2, 3, 4, 5, 6, 7, 8, 9])
    .range(['#13315C', '#134074', '#06AED5', '#FFE156', '#E8C547', '#FAA916', '#FF4B1A', '#FF3A24', '#FF1F2E', '#DF2935']);

I get the same results as the one big red heat map from above.

I feel like I am missing some fundamental understanding about d3 that is affecting how I am approaching this problem. What am I missing here?

Thank you.


Browser information:

User Agent is: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.3 Safari/605.1.15

Challenge: Visualize Data with a Heat Map

Link to the challenge:

scaleQuanitze worked for me

const variance = d3.extent(monthlyVariance, d => d.variance)
  const azules = colorbrewer.Blues[4].reverse()
  const myColor = azules.concat(colorbrewer.YlOrRd[4])
  const tempChange = d3.scaleLinear().domain(variance).range([0,w/2])
  const tempChange2 = d3.scaleQuantize().domain(variance).range(myColor)

colorbrewer has helped me a lot choose some colors and grab the index you want https://colorbrewer2.org/export/colorbrewer.js

Ah ok I did not understand where that syntax was coming from when I went to the homepage. So, I was avoiding it and trying to do it myself. This makes more sense, thank you.

What is the reason you concatenate the colors before passing them to range? Thanks.

Because the colors went from light to dark so I just played around until I got the color I liked

For these projects I’ve created and object that holds my palette of colors.

Everytime I want to determine the fill color for the domain I do this technique: I define in this case the domain from the min temperatures to max temperatures, and for the range I go from zero to the amount of keys in my palette of colors.

EDIT: I also use a Linear Scale.

1 Like

Hm, okay, thanks for that. I’ll see if I can try a similar approach.

Are you just using 1 color per key or an array of colors for every key? I guess I don’t understand why I can’t use an array of colors that go from one end to another.

I was able to solve my problem!

There were 2 issues:

  1. I was using temperature data and not variance data, since the graph is not actually about temperature but about the shift from the norm (variance).
  2. I was using scaleThreshold rathe than scaleQuantize, which would divide the variances into groups based on how many colors there are to choose from.
  // make legendScale - helps to determine color of cell
  let domainOfVariance = data.map(d => d.variance)

  
  let legendThreshold = d3
    .scaleQuantize()
    .domain(d3.extent(domainOfVariance)) 
    .range(['#13315C', '#134074', '#06AED5', '#FFE156', '#E8C547', '#FAA916', '#FF4B1A', '#FF3A24', '#FF1F2E', '#DF2935']); 
  // add cells
    svg
    .append('g')
    .attr('transform', `translate(${padding}, 0)`)
    .selectAll('rect')
    .data(data)
    .enter()
    .append('rect')
    .attr('x', (d) => xScale(d.year))
    .attr('y', (d) => yScale(months[d.month-1])) // use number data to get word month data
    .attr('width', (d) => xScale.bandwidth(d.year))
    .attr('height', (d) => yScale.bandwidth(d.month))
    .attr('data-month',  (d) => d.month)
    .attr('data-year',  (d) => d.year)
    .style('fill', (d) => legendThreshold(d.variance)); // here the threshold is determined by variance data
})

Sorry for the late reply. I’ve seen that you found another solution, that’s great!
I will post here how I did and maybe it can also be helpful for someone else.

Anyway here’s how I did it:

  1. I define the palette of color that will be used in our chart and in the legend.
    2)I find the domain, in this case the temperature range. (I need to know the minimum temperature and the maximum temperature)
  2. Once I have the two limits I create the two scales for the chart and the legend and the only thing that differenciate them is the range: for the legend it will be the size of the “div” and for the chart it will be based on the number of colors in my palette.

Here’s the code for this part:

const myColors = {
  myBlue: "#083D77",
  myCyan: "#5DB7DE",
  myYellow: "#F4D35E",
  myOrange: "#EE964B",
  myRed: "#F95738"
};

const legendColorChartSize = 200;
const legendColorWidth = legendColorChartSize / Object.keys(myColors).length;
const legendColorHeight = 20;

const minTemp =
  data.baseTemperature +
  d3.min(data.monthlyVariance, (d) => {
    return d.variance;
  });

const maxTemp =
  data.baseTemperature +
  d3.max(data.monthlyVariance, (d) => {
    return d.variance;
  });

const colorScale = d3
  .scaleLinear()
  .domain([minTemp, maxTemp])
  .range([0, legendColorChartSize]);

const colorBarScale = d3
  .scaleLinear()
  .domain([minTemp, maxTemp])
  .range([0, Object.keys(myColors).length]);

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.