block by nitaku 32893c305aa87a65129a

Multivariate binning II

Full Screen

-

index.js

(function() {
  var angle, apothem, arc_generator, bars, bins, bottom_left, bottom_right, classes, color, distributions, dots, hexbin, inner_radius_scale, max, max_tot, middle_left, middle_right, pie_layout, pies, points, radius, radius_scale, side, subplots, svg, upper_left, upper_right, x_scale, y_scale;

  d3.select(self.frameElement).style('height', '1440px');

  side = 480;

  distributions = [
    d3.range(1500).map(function() {
      return {
        x: d3.random.normal(side / 2, 80)(),
        y: d3.random.normal(side / 2, 80)()
      };
    }), d3.range(1500).map(function() {
      return {
        x: d3.random.normal(side / 2, 80)() - 50,
        y: 100 + d3.random.normal(side / 2, 80)()
      };
    }), d3.range(1500).map(function() {
      return {
        x: d3.random.normal(side / 2, 80)(),
        y: d3.random.normal(side / 2, 80)() - 100
      };
    }), d3.range(1000).map(function() {
      return {
        x: d3.random.normal(3.5 * side / 4, 80)(),
        y: d3.random.normal(side / 2, 80)()
      };
    })
  ];

  classes = distributions.length;

  points = _.chain(distributions).map(function(distribution, klass) {
    distribution.forEach(function(point) {
      return point["class"] = klass;
    });
    return distribution;
  }).flatten(true).value();

  radius = 18;

  apothem = Math.sqrt(3) / 2 * radius;

  hexbin = d3.hexbin().size([side, side]).radius(radius).x(function(d) {
    return d.x;
  }).y(function(d) {
    return d.y;
  });

  bins = _.chain(hexbin(points)).forEach(function(bin) {
    return bin.classes_count = _.chain(_.range(classes)).map(function(klass) {
      return bin.filter(function(point) {
        return point["class"] === klass;
      }).length;
    }).value();
  }).value();

  max_tot = d3.max(bins, function(bin) {
    return bin.length;
  });

  max = d3.max(bins, function(bin) {
    return d3.max(bin.classes_count);
  });

  angle = 2 * Math.PI / classes;

  svg = d3.select('svg');

  upper_left = svg.append('g').attr('id', 'dots').attr('clip-path', 'url(#square_window)');

  upper_right = svg.append('g').attr('id', 'bars').attr('clip-path', 'url(#square_window)').attr('transform', "translate(" + side + ",0)");

  middle_left = svg.append('g').attr('id', 'pies').attr('clip-path', 'url(#square_window)').attr('transform', "translate(0," + side + ")");

  middle_right = svg.append('g').attr('id', 'arcs').attr('clip-path', 'url(#square_window)').attr('transform', "translate(" + side + "," + side + ")");

  bottom_left = svg.append('g').attr('id', 'polar_length').attr('clip-path', 'url(#square_window)').attr('transform', "translate(0," + (2 * side) + ")");

  bottom_right = svg.append('g').attr('id', 'polar_area').attr('clip-path', 'url(#square_window)').attr('transform', "translate(" + side + "," + (2 * side) + ")");

  svg.append('line').attr({
    "class": 'separator',
    x1: 0,
    x2: 2 * side,
    y1: side,
    y2: side
  });

  svg.append('line').attr({
    "class": 'separator',
    x1: 0,
    x2: 2 * side,
    y1: 2 * side,
    y2: 2 * side
  });

  svg.append('line').attr({
    "class": 'separator',
    x1: side,
    x2: side,
    y1: 0,
    y2: 3 * side
  });

  color = d3.scale.category10();

  dots = d3.select('#dots').selectAll('.dot').data(points);

  dots.enter().append('circle').attr({
    "class": 'dot',
    r: 1,
    cx: function(p) {
      return p.x;
    },
    cy: function(p) {
      return p.y;
    },
    fill: function(d) {
      return color(d["class"]);
    }
  });

  subplots = d3.select('#bars').selectAll('.subplot').data(bins);

  subplots.enter().append('g').attr({
    "class": 'subplot',
    transform: function(bin) {
      return "translate(" + bin.x + "," + bin.y + ")";
    }
  });

  y_scale = d3.scale.linear().domain([0, max]).range([0, radius]);

  x_scale = d3.scale.ordinal().domain(_.range(classes)).rangeRoundBands([-radius / 2, radius / 2], 0.05);

  bars = subplots.selectAll('.bar').data(function(bin) {
    return bin.classes_count;
  });

  bars.enter().append('rect').attr({
    "class": 'bar',
    x: function(count, klass) {
      return x_scale(klass);
    },
    y: function(count) {
      return y_scale(max - count);
    },
    width: x_scale.rangeBand(),
    height: function(count) {
      return y_scale(count);
    },
    fill: function(count, klass) {
      return color(klass);
    },
    transform: "translate(0," + (-radius) + ")"
  });

  subplots = d3.select('#pies').selectAll('.subplot').data(bins);

  subplots.enter().append('g').attr({
    "class": 'subplot',
    transform: function(bin) {
      return "translate(" + bin.x + "," + bin.y + ")";
    }
  });

  pie_layout = d3.layout.pie().sort(null);

  radius_scale = d3.scale.sqrt().domain([0, max_tot]).range([0, apothem]);

  arc_generator = d3.svg.arc().outerRadius(function(count) {
    return radius_scale(d3.select(this.parentNode).datum().length);
  }).innerRadius(0);

  pies = subplots.selectAll('.pie').data(function(bin) {
    return pie_layout(bin.classes_count);
  });

  pies.enter().append('path').attr({
    "class": 'pie',
    d: arc_generator,
    fill: function(count, klass) {
      return color(klass);
    }
  });

  subplots = d3.select('#arcs').selectAll('.subplot').data(bins);

  subplots.enter().append('g').attr({
    "class": 'subplot',
    transform: function(bin) {
      return "translate(" + bin.x + "," + bin.y + ")";
    }
  });

  radius_scale = d3.scale.linear().domain([0, max]).range([0, apothem * 0.9]);

  inner_radius_scale = d3.scale.linear().domain([0, max]).range([apothem * 0.4, apothem * 0.9]);

  arc_generator = d3.svg.arc().innerRadius(function(count) {
    return inner_radius_scale(max - count);
  }).outerRadius(function(count) {
    return radius_scale(max);
  }).startAngle(function(count, klass) {
    return klass * angle - angle / 2;
  }).endAngle(function(count, klass) {
    return klass * angle + angle / 2;
  });

  pies = subplots.selectAll('.pie').data(function(bin) {
    return bin.classes_count;
  });

  pies.enter().append('path').attr({
    "class": 'pie',
    d: arc_generator,
    fill: function(count, klass) {
      return color(klass);
    }
  });

  subplots = d3.select('#polar_length').selectAll('.subplot').data(bins);

  subplots.enter().append('g').attr({
    "class": 'subplot',
    transform: function(bin) {
      return "translate(" + bin.x + "," + bin.y + ")";
    }
  });

  radius_scale = d3.scale.linear().domain([0, max]).range([0, apothem]);

  arc_generator = d3.svg.arc().innerRadius(0).outerRadius(function(count) {
    return radius_scale(count);
  }).startAngle(function(count, klass) {
    return klass * angle - angle / 2;
  }).endAngle(function(count, klass) {
    return klass * angle + angle / 2;
  });

  pies = subplots.selectAll('.pie').data(function(bin) {
    return bin.classes_count;
  });

  pies.enter().append('path').attr({
    "class": 'pie',
    d: arc_generator,
    fill: function(count, klass) {
      return color(klass);
    }
  });

  subplots = d3.select('#polar_area').selectAll('.subplot').data(bins);

  subplots.enter().append('g').attr({
    "class": 'subplot',
    transform: function(bin) {
      return "translate(" + bin.x + "," + bin.y + ")";
    }
  });

  radius_scale = d3.scale.sqrt().domain([0, max]).range([0, apothem]);

  arc_generator = d3.svg.arc().innerRadius(0).outerRadius(function(count) {
    return radius_scale(count);
  }).startAngle(function(count, klass) {
    return klass * angle - angle / 2;
  }).endAngle(function(count, klass) {
    return klass * angle + angle / 2;
  });

  pies = subplots.selectAll('.pie').data(function(bin) {
    return bin.classes_count;
  });

  pies.enter().append('path').attr({
    "class": 'pie',
    d: arc_generator,
    fill: function(count, klass) {
      return color(klass);
    }
  });

}).call(this);

index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="description" content="Multivariate binning II" />
  <title>Multivariate binning II</title>
  <link rel="stylesheet" href="index.css">
  <script src="//d3js.org/d3.v3.min.js"></script>
  <script src="//d3js.org/d3.hexbin.v0.min.js?5c6e4f0"></script>
  <script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.min.js"></script>
</head>
<body>
  <svg height="1440" width="960">
    <defs>
      <clipPath id="square_window">
        <rect x="0" y="0" width="480.5" height="480.5" />
      </clipPath>
    </defs>
  </svg>
  <script src="index.js"></script>
</body>
</html>

index.coffee

# set the frame size
d3.select(self.frameElement).style('height', '1440px')

side = 480

# DATA
distributions = [
  d3.range(1500).map( () -> {x: d3.random.normal(side/2, 80)(), y: d3.random.normal(side/2, 80)()} ),
  d3.range(1500).map( () -> {x: d3.random.normal(side/2, 80)() - 50, y: 100+d3.random.normal(side/2, 80)()} ),
  d3.range(1500).map( () -> {x: d3.random.normal(side/2, 80)(), y: d3.random.normal(side/2, 80)() - 100} ),
  d3.range(1000).map( () -> {x: d3.random.normal(3.5*side/4, 80)(), y: d3.random.normal(side/2, 80)()} )
]

classes = distributions.length

points = _.chain(distributions)
  .map( (distribution, klass) ->
    distribution.forEach (point) -> point.class = klass
    return distribution
  )
  .flatten(true)
  .value()
  
# hexagonal binning
radius = 18
apothem = Math.sqrt(3)/2 * radius

hexbin = d3.hexbin()
  .size([side, side])
  .radius(radius)
  .x((d) -> d.x)
  .y((d) -> d.y)
  
bins = _.chain(hexbin(points))
  .forEach( (bin) ->
    bin.classes_count = _.chain(_.range(classes))
      .map( (klass) -> bin.filter( (point) -> point.class is klass ).length )
      .value()
  )
  .value()

max_tot = d3.max(bins, (bin) -> bin.length)
max = d3.max(bins, (bin) -> d3.max(bin.classes_count) )
angle = 2*Math.PI / classes


svg = d3.select('svg')

upper_left = svg.append('g')
    .attr('id', 'dots')
    .attr('clip-path', 'url(#square_window)')
  
upper_right = svg.append('g')
    .attr('id', 'bars')
    .attr('clip-path', 'url(#square_window)')
    .attr('transform', "translate(#{side},0)")
    
middle_left = svg.append('g')
    .attr('id', 'pies')
    .attr('clip-path', 'url(#square_window)')
    .attr('transform', "translate(0,#{side})")
    
middle_right = svg.append('g')
    .attr('id', 'arcs')
    .attr('clip-path', 'url(#square_window)')
    .attr('transform', "translate(#{side},#{side})")
    
bottom_left = svg.append('g')
    .attr('id', 'polar_length')
    .attr('clip-path', 'url(#square_window)')
    .attr('transform', "translate(0,#{2*side})")
    
bottom_right = svg.append('g')
    .attr('id', 'polar_area')
    .attr('clip-path', 'url(#square_window)')
    .attr('transform', "translate(#{side},#{2*side})")
    
svg.append('line')
  .attr
    class: 'separator'
    x1: 0
    x2: 2*side
    y1: side
    y2: side
  
svg.append('line')
  .attr
    class: 'separator'
    x1: 0
    x2: 2*side
    y1: 2*side
    y2: 2*side
    
svg.append('line')
  .attr
    class: 'separator'
    x1: side
    x2: side
    y1: 0
    y2: 3*side
  
#color = d3.scale.ordinal()
#  .domain([0, classes])
#  .range(["#1b9e77","#d95f02","#66a61e","#e6ab02","#e7298a","#a6761d","#666666"])
color = d3.scale.category10()

# dot density plot
dots = d3.select('#dots').selectAll('.dot')
  .data(points)

dots.enter().append('circle')
  .attr
    class: 'dot'
    r: 1
    cx: (p) -> p.x
    cy: (p) -> p.y
    fill: (d) -> color(d.class)
    
# bar chart subplotting
subplots = d3.select('#bars').selectAll('.subplot')
  .data(bins)
  
subplots.enter().append('g')
  .attr
    class: 'subplot'
    transform: (bin) -> "translate(#{bin.x},#{bin.y})"
    
y_scale = d3.scale.linear()
  .domain([0, max])
  .range([0, radius])
  
x_scale = d3.scale.ordinal()
  .domain(_.range(classes))
  .rangeRoundBands([-radius/2, radius/2], 0.05)
    
bars = subplots.selectAll('.bar')
  .data((bin) -> bin.classes_count)
  
bars.enter().append('rect')
  .attr
    class: 'bar'
    x: (count, klass) -> x_scale(klass)
    y: (count) -> y_scale(max-count)
    width: x_scale.rangeBand()
    height: (count) -> y_scale(count)
    fill: (count, klass) -> color(klass)
    transform: "translate(0,#{-radius})"
    
# pie chart subplotting
subplots = d3.select('#pies').selectAll('.subplot')
  .data(bins)
  
subplots.enter().append('g')
  .attr
    class: 'subplot'
    transform: (bin) -> "translate(#{bin.x},#{bin.y})"
    
pie_layout = d3.layout.pie()
  .sort(null)
  
radius_scale = d3.scale.sqrt()
  .domain([0, max_tot])
  .range([0, apothem])
  
arc_generator = d3.svg.arc()
  .outerRadius((count) -> radius_scale( d3.select(this.parentNode).datum().length ))
  .innerRadius(0)
    
pies = subplots.selectAll('.pie')
  .data((bin) -> pie_layout(bin.classes_count))
  
pies.enter().append('path')
  .attr
    class: 'pie'
    d: arc_generator
    fill: (count, klass) -> color(klass)

# arc chart subplotting
subplots = d3.select('#arcs').selectAll('.subplot')
  .data(bins)
  
subplots.enter().append('g')
  .attr
    class: 'subplot'
    transform: (bin) -> "translate(#{bin.x},#{bin.y})"
    
radius_scale = d3.scale.linear()
  .domain([0, max])
  .range([0, apothem*0.9])
  
inner_radius_scale = d3.scale.linear()
  .domain([0, max])
  .range([apothem*0.4, apothem*0.9])
  
arc_generator = d3.svg.arc()
  .innerRadius((count) -> inner_radius_scale(max-count))
  .outerRadius((count) -> radius_scale(max))
  .startAngle((count, klass) -> klass*angle - angle/2)
  .endAngle((count, klass) -> klass*angle + angle/2)
  
pies = subplots.selectAll('.pie')
  .data((bin) -> bin.classes_count)
  
pies.enter().append('path')
  .attr
    class: 'pie'
    d: arc_generator
    fill: (count, klass) -> color(klass)

# polar length chart subplotting
subplots = d3.select('#polar_length').selectAll('.subplot')
  .data(bins)
  
subplots.enter().append('g')
  .attr
    class: 'subplot'
    transform: (bin) -> "translate(#{bin.x},#{bin.y})"
    
radius_scale = d3.scale.linear()
  .domain([0, max])
  .range([0, apothem])

arc_generator = d3.svg.arc()
  .innerRadius(0)
  .outerRadius((count) -> radius_scale(count))
  .startAngle((count, klass) -> klass*angle - angle/2)
  .endAngle((count, klass) -> klass*angle + angle/2)
  
pies = subplots.selectAll('.pie')
  .data((bin) -> bin.classes_count)
  
pies.enter().append('path')
  .attr
    class: 'pie'
    d: arc_generator
    fill: (count, klass) -> color(klass)


# polar area chart subplotting
subplots = d3.select('#polar_area').selectAll('.subplot')
  .data(bins)
  
subplots.enter().append('g')
  .attr
    class: 'subplot'
    transform: (bin) -> "translate(#{bin.x},#{bin.y})"
    
radius_scale = d3.scale.sqrt()
  .domain([0, max])
  .range([0, apothem])

arc_generator = d3.svg.arc()
  .innerRadius(0)
  .outerRadius((count) -> radius_scale(count))
  .startAngle((count, klass) -> klass*angle - angle/2)
  .endAngle((count, klass) -> klass*angle + angle/2)
  
pies = subplots.selectAll('.pie')
  .data((bin) -> bin.classes_count)
  
pies.enter().append('path')
  .attr
    class: 'pie'
    d: arc_generator
    fill: (count, klass) -> color(klass)

index.css

svg {
  background-color: white;
}
.separator {
  stroke: #DEDEDE;
  fill: none;
  shape-rendering: crispEdges;
}
.outer_polygon {
  fill: #EEE;
}

.pie {
  stroke: white;
  stroke-width: 0.5;
}

#pies .pie {
  stroke: none;
}