Last active
August 29, 2015 14:03
-
-
Save nitaku/38e263524da7ef85e11f to your computer and use it in GitHub Desktop.
Multivariate binning
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
side = 320 | |
# 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)()} ), | |
#d3.range(1000).map( () -> {x: d3.random.normal(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 = 26 | |
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', 'pies') | |
.attr('clip-path', 'url(#square_window)') | |
upper_middle = svg.append('g') | |
.attr('id', 'dots') | |
.attr('clip-path', 'url(#square_window)') | |
.attr('transform', "translate(#{side},0)") | |
upper_right = svg.append('g') | |
.attr('id', 'radar') | |
.attr('clip-path', 'url(#square_window)') | |
.attr('transform', "translate(#{2*side},0)") | |
bottom_left = svg.append('g') | |
.attr('id', 'polar_length') | |
.attr('clip-path', 'url(#square_window)') | |
.attr('transform', "translate(#{side/2},#{side})") | |
bottom_right = svg.append('g') | |
.attr('id', 'polar_area') | |
.attr('clip-path', 'url(#square_window)') | |
.attr('transform', "translate(#{3*side/2},#{side})") | |
svg.append('line') | |
.attr | |
class: 'separator' | |
x1: 0 | |
x2: 3*side | |
y1: side | |
y2: side | |
svg.append('line') | |
.attr | |
class: 'separator' | |
x1: side | |
x2: side | |
y1: 0 | |
y2: side | |
svg.append('line') | |
.attr | |
class: 'separator' | |
x1: 2*side | |
x2: 2*side | |
y1: 0 | |
y2: side | |
svg.append('line') | |
.attr | |
class: 'separator' | |
x1: side/2 | |
x2: side/2 | |
y1: side | |
y2: 2*side | |
svg.append('line') | |
.attr | |
class: 'separator' | |
x1: 3*side/2 | |
x2: 3*side/2 | |
y1: side | |
y2: 2*side | |
svg.append('line') | |
.attr | |
class: 'separator' | |
x1: 5*side/2 | |
x2: 5*side/2 | |
y1: side | |
y2: 2*side | |
#color = d3.scale.ordinal() | |
# .domain([0, classes]) | |
# .range(["#1b9e77","#d95f02","#66a61e","#e6ab02","#e7298a","#a6761d","#666666"]) | |
color = d3.scale.category10() | |
# dot density plot | |
dots = upper_middle.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) | |
# pie chart subplotting | |
subplots = upper_left.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) | |
# radar chart subplotting | |
subplots = upper_right.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]) | |
outer_polygon_generator = d3.svg.line() | |
.x((count, klass) -> apothem * Math.cos(klass*angle-Math.PI/2)) | |
.y((count, klass) -> apothem * Math.sin(klass*angle-Math.PI/2)) | |
polygon_generator = d3.svg.line() | |
.x((count, klass) -> radius_scale(count) * Math.cos(klass*angle-Math.PI/2)) | |
.y((count, klass) -> radius_scale(count) * Math.sin(klass*angle-Math.PI/2)) | |
subplots.append('path') | |
.attr | |
class: 'outer_polygon' | |
d: (bin) -> outer_polygon_generator(bin.classes_count) + 'z' | |
subplots.append('path') | |
.attr | |
class: 'polygon' | |
d: (bin) -> polygon_generator(bin.classes_count) + 'z' | |
# polar length chart subplotting | |
subplots = bottom_left.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 = bottom_right.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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<meta name="description" content="Multivariate binning" /> | |
<title>Multivariate binning</title> | |
<link rel="stylesheet" href="index.css"> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<script src="http://d3js.org/d3.hexbin.v0.min.js?5c6e4f0"></script> | |
<script src="http://cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.min.js"></script> | |
</head> | |
<body> | |
<svg height="640" width="960"> | |
<defs> | |
<clipPath id="square_window"> | |
<rect x="0" y="0" width="320.5" height="320.5" /> | |
</clipPath> | |
</defs> | |
</svg> | |
<script src="index.js"></script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function() { | |
var angle, apothem, arc_generator, bins, bottom_left, bottom_right, classes, color, distributions, dots, hexbin, max, max_tot, outer_polygon_generator, pie_layout, pies, points, polygon_generator, radius, radius_scale, side, subplots, svg, upper_left, upper_middle, upper_right; | |
side = 320; | |
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(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 = 26; | |
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', 'pies').attr('clip-path', 'url(#square_window)'); | |
upper_middle = svg.append('g').attr('id', 'dots').attr('clip-path', 'url(#square_window)').attr('transform', "translate(" + side + ",0)"); | |
upper_right = svg.append('g').attr('id', 'radar').attr('clip-path', 'url(#square_window)').attr('transform', "translate(" + (2 * side) + ",0)"); | |
bottom_left = svg.append('g').attr('id', 'polar_length').attr('clip-path', 'url(#square_window)').attr('transform', "translate(" + (side / 2) + "," + side + ")"); | |
bottom_right = svg.append('g').attr('id', 'polar_area').attr('clip-path', 'url(#square_window)').attr('transform', "translate(" + (3 * side / 2) + "," + side + ")"); | |
svg.append('line').attr({ | |
"class": 'separator', | |
x1: 0, | |
x2: 3 * side, | |
y1: side, | |
y2: side | |
}); | |
svg.append('line').attr({ | |
"class": 'separator', | |
x1: side, | |
x2: side, | |
y1: 0, | |
y2: side | |
}); | |
svg.append('line').attr({ | |
"class": 'separator', | |
x1: 2 * side, | |
x2: 2 * side, | |
y1: 0, | |
y2: side | |
}); | |
svg.append('line').attr({ | |
"class": 'separator', | |
x1: side / 2, | |
x2: side / 2, | |
y1: side, | |
y2: 2 * side | |
}); | |
svg.append('line').attr({ | |
"class": 'separator', | |
x1: 3 * side / 2, | |
x2: 3 * side / 2, | |
y1: side, | |
y2: 2 * side | |
}); | |
svg.append('line').attr({ | |
"class": 'separator', | |
x1: 5 * side / 2, | |
x2: 5 * side / 2, | |
y1: side, | |
y2: 2 * side | |
}); | |
color = d3.scale.category10(); | |
dots = upper_middle.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 = upper_left.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 = upper_right.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]); | |
outer_polygon_generator = d3.svg.line().x(function(count, klass) { | |
return apothem * Math.cos(klass * angle - Math.PI / 2); | |
}).y(function(count, klass) { | |
return apothem * Math.sin(klass * angle - Math.PI / 2); | |
}); | |
polygon_generator = d3.svg.line().x(function(count, klass) { | |
return radius_scale(count) * Math.cos(klass * angle - Math.PI / 2); | |
}).y(function(count, klass) { | |
return radius_scale(count) * Math.sin(klass * angle - Math.PI / 2); | |
}); | |
subplots.append('path').attr({ | |
"class": 'outer_polygon', | |
d: function(bin) { | |
return outer_polygon_generator(bin.classes_count) + 'z'; | |
} | |
}); | |
subplots.append('path').attr({ | |
"class": 'polygon', | |
d: function(bin) { | |
return polygon_generator(bin.classes_count) + 'z'; | |
} | |
}); | |
subplots = bottom_left.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 = bottom_right.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); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment