Imi
Imi

Reputation: 549

How to fill number of rectangles in a row corresponding to the numbers in an array. d3.js

I have 100 rectangles arranged in a 10x10 square. I want to color the number of rectangles according to the numbers in the following array

var avg = [1, 4, 4, 7, 11, 15, 58]

I am stuck at the value 4 since it is appearing twice plus the code looks really ugly. Is there a nicer way to achieve this without using if/else?

The output should be 1 rectangle should have color from colors[0] then 4 rectangles from colors1 and the next 4 from colors[2] and so on. JS fiddle here is my code:

var avg = [1, 4, 4, 7, 11, 15, 58]
var colors = ['#009BFF', '#AAC30A', '#DC0f6e', '#82b905', '#96be00', '#C8D205',
  '#82141E', '#BE232D', '#E14614', '#EB6E14', '#EB8614', '#F0AA00'
]

var ContainerWidth = document.querySelector('#mainContainer').offsetWidth;
var rectWidth = ContainerWidth / 20
var svgContainer = d3.select("#boxy")

var rectangle = svgContainer.selectAll("rect")
  .data(d3.range(100));

var rectangle = rectangle.enter()
  .append("rect")
  .style("stroke", "#fff")
  .style("fill", function(d, i){ 
  for(var k=0; k<avg.length; k++){
  if(i<=avg[0]-1){
            return colors[0]
          } else if(i<=avg[1] && i>=avg[0]-1){
            return colors[1]
          } else if(i<=avg[2]-1 && i>=avg[1]-1 || avg[2]-1===avg[1]-1){
            return colors[2]
          } else if(i<=avg[3]-1 && i>=avg[2]-1){
            return colors[3]

          }
  }
  })
  .attr("x", function(d, i) {
    return i % 10 * 45
  })
  .attr("y", function(d, i) {
    return Math.floor(i / 10) % 10 * 45
  })
  .attr("width", rectWidth)
  .attr("height", rectWidth);

Upvotes: 0

Views: 111

Answers (1)

Andrew Reid
Andrew Reid

Reputation: 38171

There are a few approaches you could take. The primary challenge is that your data array doesn't have 100 elements to match to your 100 rectangles. As is you cannot bind that data to your svg elements.

You could make a data array that is 100 items long, and use that to create rectangles (rather than d3.range(100)). Or you could use a custom function while appending the rectangles to determine color.


For the first approach, you could make a data variable that is 100 items long (or whatever the total of all your avg array elements is):

var avg = [1, 4, 4, 7, 11, 15, 58];
var data = [];
var category = 0;

for (i = 0; i < avg.length; i++) {
    for (j=0; j < avg[i]; j++) {
    data.push(category); 
  }
  category++;
}

data: [ 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 90 more… ]

And then you can use this data array as your data source when appending rectangles, which allows you to assign color relatively easily:

var rectangle = svgContainer.selectAll("rect")
  .data(data)
    .enter()
  .append("rect")
  .style("fill", function(d) { return colors[d]; })

Fiddle: https://jsfiddle.net/e44y72v8/


Or, you could keep d3.range(100) to append 100 rectangles but use a function to determine what color should be applied. This requires keeping track of the cumulative total of the array's elements as we progress through the 100 rectangles. This requires two new variables:

var currentIndex = 0; // What element of the array are we at
var cumulative = 0;   // What is the cumulative total of all array elements between 0 and current index.

And your function could look like:

   .style("fill", function(d, i){ 
     if (i == cumulative) {
        cumulative += avg[currentIndex+1];
        return colors[currentIndex++];
      }
     else {
            return colors[currentIndex];      
     }

  })

Fiddle: https://jsfiddle.net/sttL9vaz/

Upvotes: 2

Related Questions