Similarity matrix in D3 I have some code that produces a similarity matrix. However, despite feeling happy with the output, I'm not convinced that my code is the most efficient or the most pleasing on the eye.

I'm looking to make it more efficient and tidier, as well as open to advice on what could be implemented to make the end product better.

var data = [ 
  [["Arsenal", 0.0], ["Chelsea", 0.6014876082652767], ["Liverpool", 0.5204181171517794],["ManchesterCity", 0.549210189254557], ["ManchesterUnited", 0.5440890632512689], ["Tottenham", 0.6304670189118691]],
  [["Arsenal",0.6014876082652767], ["Chelsea",0.0], ["Liverpool",0.5507313736526684],["ManchesterCity",0.5559069243804156], ["MancheserUnited",0.5231358671618266], ["Tottenham",0.6508134781353688]],
  [["Arsenal",0.5204181171517794], ["Chelsea",0.5507313736526684], ["Liverpool",0.0],["ManchesterCity",0.49759390310994533], ["MancheserUnited",0.4787550034617063], ["Tottenham",0.5749363562907429]],
  [["Arsenal",0.549210189254557], ["Chelsea",0.5559069243804156], ["Liverpool",0.49759390310994533],["ManchesterCity",0.0,], ["MancheserUnited",0.50215325905151], ["Tottenham",0.5802928689025063]],
  [["Arsenal",0.5440890632512689], ["Chelsea",0.5231358671618266], ["Liverpool",0.4787550034617063],["ManchesterCity",0.50215325905151], ["MancheserUnited",0.0], ["Tottenham",0.5497016431211542]],
  [["Arsenal",0.6304670189118691], ["Chelsea",0.6508134781353688], ["Liverpool",0.5749363562907429],["ManchesterCity",0.5802928689025063], ["MancheserUnited",0.5497016431211542], ["Tottenham",0.0]]
];

var teams = ["ARS", "CHE", "LIV", "MCI", "MUN", "TOT"]

var cols = data.length;
var rows = data.length;
var cellSize = 55;

var svg = d3.select("body")
    .append("svg")
    .attr("width", 600 )
    .attr("height", 500)

svg.selectAll("g")
    .data(data)
    .enter()
    .append("g")
    .attr("transform", function (d, i) {
      return "translate(" + i * cellSize + ")"
    })
    .selectAll("rect")
    .data(function(d) {return d;})
    .enter()
    .append("rect")
    .attr("fill", function(d) {
        if (d[1] == 0) {
            return "#2A363B";
        } else if (d[1] &lt;= 0.50) {
            return "#F8B195";
        } else if (d[1] &lt;= 0.55) {
            return "#F67280";
        } else if (d[1] &lt;= 0.59) {
            return "#C06C84";
        } else if (d[1] &lt;= 0.62) {
            return "#6C5B7B";
        } else if (d[1] &gt;= 0.63) {
            return "#355C7D";
        }})
    .attr("x", 100)
    .attr("y", function(d, i) {
      return i * cellSize ;
    })
    .attr("width", 50)
    .attr("height", 50)
    .attr("r", 55);
Here are some advices regarding the D3 part of the code:

Use scales!

Your if... else logic for filling the rectangles is cumbersome and luckily unnecessary: you can use a scale, in that case a threshold scale:

var color = d3.scaleThreshold()
    .range(["#F8B195", "#F67280", "#C06C84", "#6C5B7B", "#355C7D"])
    .domain([0.5, 0.55, 0.59, 0.62]);


Then, you can do just this in the attr method:

.attr("fill", function(d) {
    return d[1] === 0 ? "#2A363B" : color(d[1])
});


Do the same for the positions. Right now, you're using a lot of magic numbers everywhere: for translating the groups, positioning the rectangles and setting their width/height.

Instead of that, set up band scales:

var horizontalGrid = d3.scaleBand()
    .range([100, 400])
    .domain(d3.range(cols))
    .padding(0.1);

var verticalGrid = d3.scaleBand()
    .range([0, 300])
    .domain(d3.range(rows))
    .padding(0.1);


Using them for translating the groups...

.attr("transform", function(_, i) {
    return "translate(" + horizontalGrid(i) + ")"
})


...and positioning the rectangles, as well as setting the dimensions:

.attr("y", function(d, i) {
    return verticalGrid(i);
})
.attr("width", horizontalGrid.bandwidth())
.attr("height", verticalGrid.bandwidth());


Finally, two important advices:

Always name your selections

That's very important when the visualisation becomes more and more complex. For instance, you should do:

var groups = svg.selectAll("g")
    //etc...


Break your selections

By breaking your selections not only you can easily reference the correct selection, but you can also avoid things (silently) breaking. And it's way easy to maintain. For instance, in your code:

var groups = svg.selectAll("g")
    //etc...

var rects = groups.selectAll("rect")
    //etc...


Now you have two selections, groups (which refers to the containing &lt;g&gt; elements) and rects (which refers to the rectangles).

Select nothing

You read it right. If you don't plan to have an update selection here, just select nothing in your enter selections, or for a cleaner code select null. So, since you're not selecting anything, you have an enter selection which always contains all the data points, and a way faster code.

So, the move snippet becomes:

var groups = svg.selectAll(null)
    //etc...

var rects = groups.selectAll(null)
    //etc...


If you want to read more about selectAll(null) read my explanation here: Selecting null: what is the reason behind 'selectAll(null)' in D3.js?.

Demo

Here is your code with those changes:



var data = [
  [
    ["Arsenal", 0.0],
    ["Chelsea", 0.6014876082652767],
    ["Liverpool", 0.5204181171517794],
    ["ManchesterCity", 0.549210189254557],
    ["ManchesterUnited", 0.5440890632512689],
    ["Tottenham", 0.6304670189118691]
  ],
  [
    ["Arsenal", 0.6014876082652767],
    ["Chelsea", 0.0],
    ["Liverpool", 0.5507313736526684],
    ["ManchesterCity", 0.5559069243804156],
    ["MancheserUnited", 0.5231358671618266],
    ["Tottenham", 0.6508134781353688]
  ],
  [
    ["Arsenal", 0.5204181171517794],
    ["Chelsea", 0.5507313736526684],
    ["Liverpool", 0.0],
    ["ManchesterCity", 0.49759390310994533],
    ["MancheserUnited", 0.4787550034617063],
    ["Tottenham", 0.5749363562907429]
  ],
  [
    ["Arsenal", 0.549210189254557],
    ["Chelsea", 0.5559069243804156],
    ["Liverpool", 0.49759390310994533],
    ["ManchesterCity", 0.0, ],
    ["MancheserUnited", 0.50215325905151],
    ["Tottenham", 0.5802928689025063]
  ],
  [
    ["Arsenal", 0.5440890632512689],
    ["Chelsea", 0.5231358671618266],
    ["Liverpool", 0.4787550034617063],
    ["ManchesterCity", 0.50215325905151],
    ["MancheserUnited", 0.0],
    ["Tottenham", 0.5497016431211542]
  ],
  [
    ["Arsenal", 0.6304670189118691],
    ["Chelsea", 0.6508134781353688],
    ["Liverpool", 0.5749363562907429],
    ["ManchesterCity", 0.5802928689025063],
    ["MancheserUnited", 0.5497016431211542],
    ["Tottenham", 0.0]
  ]
];

var teams = ["ARS", "CHE", "LIV", "MCI", "MUN", "TOT"];

var width = 600,
  height = 500;

var cols = data.length;
var rows = data.length;
var cellSize = 55;

var horizontalGrid = d3.scaleBand()
  .range([100, 400])
  .domain(d3.range(cols))
  .padding(0.1);

var verticalGrid = d3.scaleBand()
  .range([0, 300])
  .domain(d3.range(rows))
  .padding(0.1);

var color = d3.scaleThreshold()
  .range(["#F8B195", "#F67280", "#C06C84", "#6C5B7B", "#355C7D"])
  .domain([0.5, 0.55, 0.59, 0.62])

var svg = d3.select("body")
  .append("svg")
  .attr("width", width)
  .attr("height", height)

var groups = svg.selectAll(null)
  .data(data)
  .enter()
  .append("g")
  .attr("transform", function(_, i) {
    return "translate(" + horizontalGrid(i) + ")"
  });
  
var rects = groups.selectAll(null)
  .data(function(d) {
    return d;
  })
  .enter()
  .append("rect")
  .attr("fill", function(d) {
    return d[1] === 0 ? "#2A363B" : color(d[1])
  })
  .attr("y", function(d, i) {
    return verticalGrid(i);
  })
  .attr("width", horizontalGrid.bandwidth())
  .attr("height", verticalGrid.bandwidth());
&lt;script src="https://d3js.org/d3.v5.min.js"&gt;&lt;/script&gt;