block by nitaku a1f7c35684c1a78589f1b114fc5fc5a5

Rectangular adjacency matrix

Full Screen

A simple example of a rectangular adjacency matrix. The scaleBand.padding() method is not used because it is less flexible than using pixel math. This way, we can use pixel-based paddings and still be able to use the scale to address a specific cell without having to correct pixel values by taking the unknown padding amount into account.

index.js

// Generated by CoffeeScript 1.10.0
(function() {
  var a_ids, a_n, all_cells, b_ids, b_n, background, cells, cellsize, en_cells, height, links, matrix_h, matrix_w, svg, vis, width, x, y;

  links = [
    {
      a: 'Italy',
      b: 'Italian'
    }, {
      a: 'Germany',
      b: 'German'
    }, {
      a: 'France',
      b: 'French'
    }, {
      a: 'Spain',
      b: 'Spanish'
    }, {
      a: 'Switzerland',
      b: 'Italian'
    }, {
      a: 'Switzerland',
      b: 'German'
    }, {
      a: 'Switzerland',
      b: 'French'
    }
  ];

  a_ids = (d3.set(links.map(function(d) {
    return d.a;
  }))).values();

  b_ids = (d3.set(links.map(function(d) {
    return d.b;
  }))).values();

  matrix_w = 400;

  matrix_h = 400;

  a_n = a_ids.length;

  b_n = b_ids.length;

  cellsize = Math.min(matrix_w / b_n, matrix_h / a_n);

  a_ids.sort();

  b_ids.sort();

  x = d3.scaleBand().domain(b_ids).range([-cellsize * b_n / 2, cellsize * b_n / 2]);

  y = d3.scaleBand().domain(a_ids).range([-cellsize * a_n / 2, cellsize * a_n / 2]);

  svg = d3.select('svg');

  width = svg.node().getBoundingClientRect().width;

  height = svg.node().getBoundingClientRect().height;

  vis = svg.append('g').attrs({
    transform: "translate(" + (width / 2) + "," + (height / 2) + ")"
  });

  background = vis.append('rect').attrs({
    "class": 'background',
    x: x.range()[0],
    y: y.range()[0],
    width: x.range()[1] - x.range()[0],
    height: y.range()[1] - y.range()[0]
  });

  cells = vis.selectAll('.cell').data(links, function(d) {
    return d.a + "--" + d.b;
  });

  en_cells = cells.enter().append('rect').attrs({
    "class": 'cell'
  });

  all_cells = en_cells.merge(cells);

  all_cells.attrs({
    x: function(d) {
      return x(d.b) + 0.5;
    },
    y: function(d) {
      return y(d.a) + 0.5;
    },
    width: x.bandwidth() - 1,
    height: y.bandwidth() - 1
  });

  cells.exit().remove();

}).call(this);

index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Rectangular adjacency matrix</title>
  <link type="text/css" href="index.css" rel="stylesheet"/>
  <script src="https://d3js.org/d3.v4.min.js"></script>
  <script src="https://d3js.org/d3-selection-multi.v0.4.min.js"></script>
</head>
<body>
  <svg></svg>
  <script src="index.js"></script>
</body>
</html>

index.coffee

# DATA

links = [
  {a: 'Italy', b: 'Italian'},
  {a: 'Germany', b: 'German'},
  {a: 'France', b: 'French'},
  {a: 'Spain', b: 'Spanish'},
  {a: 'Switzerland', b: 'Italian'},
  {a: 'Switzerland', b: 'German'},
  {a: 'Switzerland', b: 'French'}
]

a_ids = (d3.set (links.map (d) -> d.a)).values()
b_ids = (d3.set (links.map (d) -> d.b)).values()

# LAYOUT

# try to fit this rectangle with the matrix
matrix_w = 400
matrix_h = 400

a_n = a_ids.length
b_n = b_ids.length

cellsize = Math.min matrix_w/b_n, matrix_h/a_n # beware! a is y and b is x!

a_ids.sort()
b_ids.sort()

x = d3.scaleBand()
  .domain b_ids # beware! a is y and b is x!
  .range [-cellsize*b_n/2, cellsize*b_n/2] # centered in (0,0)
  
y = d3.scaleBand()
  .domain a_ids
  .range [-cellsize*a_n/2, cellsize*a_n/2]

# VIS

svg = d3.select 'svg'
width = svg.node().getBoundingClientRect().width
height = svg.node().getBoundingClientRect().height

# center the vis on (0,0)
vis = svg.append 'g'
  .attrs
    transform: "translate(#{width/2},#{height/2})"
    
# draw a background
background = vis.append 'rect'
  .attrs
    class: 'background'
    x: x.range()[0]
    y: y.range()[0]
    width: x.range()[1]-x.range()[0]
    height: y.range()[1]-y.range()[0]

# draw cells
cells = vis.selectAll '.cell'
  .data links, (d) -> "#{d.a}--#{d.b}"
  
en_cells = cells.enter().append 'rect'
  .attrs
    class: 'cell'
      
all_cells = en_cells.merge cells

all_cells
  .attrs
    x: (d) -> x(d.b) + 0.5
    y: (d) -> y(d.a) + 0.5
    width: x.bandwidth() - 1
    height: y.bandwidth() - 1
    
cells.exit().remove()

index.css

body, html {
  padding: 0;
  margin: 0;
  height: 100%;
}
svg {
  width: 100%;
  height: 100%;
  background: white;
}

.background {
  fill: #EEE;
}

.cell {
  fill: teal;
  shape-rendering: crispEdges;
}