Skip to content

Instantly share code, notes, and snippets.

@nitaku
Last active October 19, 2015 13:28
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nitaku/32893c305aa87a65129a to your computer and use it in GitHub Desktop.
Save nitaku/32893c305aa87a65129a to your computer and use it in GitHub Desktop.
Multivariate binning II
# 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)
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;
}
<!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="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="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>
(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);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment