Skip to content

Instantly share code, notes, and snippets.

@nitaku
Last active August 29, 2015 14:03
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save nitaku/afd691f5b05edc8b6e82 to your computer and use it in GitHub Desktop.
OpeNER - Places with categories (Paris, bubble binning)
# Setup
width = 960
height = 500
svg = d3.select('svg')
# append a group for zoomable content
zoom_group = svg.append('g')
# define a zoom behavior
zoom = d3.behavior.zoom()
.scaleExtent([1,4]) # min-max zoom
.on('zoom', () ->
# whenever the user zooms,
# modify translation and scale of the zoom group accordingly
zoom_group.attr('transform', "translate(#{zoom.translate()})scale(#{zoom.scale()})")
)
# bind the zoom behavior to the main SVG
svg.call(zoom)
# Lambert equal-area projection - EU standard for statistical maps
projection = d3.geo.azimuthalEqualArea()
.clipAngle(180 - 1e-3)
.scale(280000)
.rotate([-2.340087890624995, -48.858643406835796, 0])
.translate([width / 2, height / 2])
.precision(0.1)
path_generator = d3.geo.path()
.projection(projection)
categories = ['restaurant', 'attraction', 'poi', 'accommodation']
classes = categories.length
colorify = d3.scale.category10()
.domain(categories)
geo_url = 'http://wafi.iit.cnr.it/webvis/tmp/paris.topojson'
data_url = 'http://wafi.iit.cnr.it/webvis/tmp/paris.json'
d3.json geo_url, (geo) ->
regions = topojson.feature(geo, geo.objects['conseils-quartiers'])
zoom_group.selectAll('.region')
.data(regions.features)
.enter().append('path')
.attr('class', 'region')
.attr('d', path_generator)
zoom_group.append('path')
.attr
class: 'water'
transform: 'translate(45,-206) scale(1.1)'
d: "m 341.4375,154.84375 -27.78125,13.125 -27.78125,10.625 -8.09375,5.53125 -21.71875,16.6875 -10.09375,8.0625 -16.15625,10.625 -14.65625,6.0625 -19.6875,10.09375 -16.65625,8.59375 -8.59375,8.0625 -12.625,19.71875 -10.625,10.09375 -14.125,21.21875 -13.65625,18.1875 L 99,343.75 83.84375,361.9375 c 0,0 -13.146098,16.66211 -14.15625,19.1875 -1.010153,2.52538 -5.03125,17.65625 -5.03125,17.65625 l -9.09375,35.875 1.5,7.0625 2.53125,13.65625 -1,15.125 L 56.0625,485.15625 51,495.25 53.03125,515.96875 56.0625,520 l 5.0625,11.625 -1.53125,11.125 5.0625,11.09375 12.625,16.15625 11.625,8.59375 8.0625,1.53125 19.71875,-7.09375 13.625,-6.0625 7.5625,-4.03125 14.65625,-15.15625 12.625,-11.09375 13.65625,-21.21875 9.0625,-13.625 19.21875,-29.3125 L 217.1875,461.4375 235.375,443.75 246.46875,428.59375 249,419 l 9.09375,-10.09375 9.59375,-7.59375 12.125,-5.03125 29.28125,-2.03125 24.75,-0.5 32.84375,15.65625 13.625,6.5625 6.0625,0.5 7.59375,3.53125 11.09375,12.125 13.15625,8.09375 14.625,7.5625 21.71875,12.625 15.65625,15.15625 2.03125,6.5625 15.65625,17.1875 7.5625,10.09375 5.5625,7.5625 c 0,0 21.70859,24.76094 22.71875,26.78125 1.01015,2.0203 17.1875,20.71875 17.1875,20.71875 l 17.15625,17.15625 19.71875,10.625 13.625,6.0625 6.5625,6.5625 -1,12.625 -3.53125,22.71875 2.53125,13.125 9.09375,12.125 7.0625,10.625 4.03125,3.03125 6.5625,9.0625 8.59375,8.59375 4.03125,15.65625 -4.03125,17.1875 -22.71875,55.03125 24.75,-45.4375 5.03125,-22.71875 -0.5,-17.1875 -7.0625,-13.125 -8.59375,-11.625 -10.09375,-10.09375 -7.59375,-8.59375 -6.0625,-8.59375 -0.5,-8.5625 2.53125,-15.15625 2.53125,-17.1875 -2.53125,-4.03125 10.09375,1 15.15625,-2.53125 9.59375,1.53125 10.625,-1.53125 17.15625,3.5625 11.125,-2.03125 10.09375,-1.53125 18.6875,0.53125 -4.53125,-0.53125 -16.6875,-3.53125 -7.0625,1.53125 -8.59375,2.53125 -13.625,-2.53125 -12.625,2.53125 -10.09375,-3.03125 c 0,0 -11.63087,2 -14.15625,2 -2.52538,0 -12.625,-0.5 -12.625,-0.5 L 596.5,594.25 583.875,589.71875 566.6875,580.625 554.0625,569 540.4375,555.875 528.8125,540.71875 513.15625,520.53125 496,498.8125 485.875,487.1875 472.75,473.03125 457.09375,456.375 l -1.5,-5.5625 -1.03125,-6.5625 -14.65625,-7.5625 -13.625,-10.625 -19.6875,-7.0625 -13.125,-5.5625 -11.125,-1.5 L 367.1875,405.375 349,396.78125 l -16.15625,-9.09375 -13.125,0 -10.625,0 -25.25,1.5 -21.21875,7.59375 -9.59375,12.125 -12.625,16.65625 -23.71875,25.75 -24.25,36.375 -20.21875,30.3125 -13.625,19.6875 -9.09375,7.0625 -10.09375,11.625 -10.09375,7.0625 -11.125,6.0625 -12.125,1.03125 L 95.96875,567.5 82.3125,558.40625 75.25,554.34375 67.6875,543.75 l 0,-2.53125 0,-7.5625 -1.53125,-5.5625 -4.53125,-6.5625 -4.03125,-6.5625 -1.03125,-13.65625 3.03125,-10.09375 7.59375,-25.75 -2.53125,-12.625 -3.03125,-16.15625 3.53125,-25.28125 7.5625,-26.75 6.21875,-11.125 3.53125,-5.4375 18.53125,-6.6875 14.15625,-13.625 11.125,-18.1875 15.125,-23.21875 2.9375,-6.3125 -10.5,15.40625 L 123.25,327.5625 116.15625,344.25 99,358.375 l -14.96875,5.8125 1.8125,-2.375 8.84375,-10 18.96875,-20.6875 36.84375,-44.46875 0.625,-1.03125 -6.78125,14.4375 2.625,-3.8125 4.71875,-11.53125 11.96875,-19.78125 12.625,-13.625 27.78125,-15.65625 17.65625,-4.5625 L 241.4375,220 l 28.28125,-22.21875 20.1875,-14.15625 41.4375,-17.65625 10.09375,-11.125 z m 51.78125,262.6875 c 0,0 11.96875,4.11161 13.21875,4.46875 1.25,0.35714 16.25,6.78125 16.25,6.78125 l 6.96875,4.125 c 0.17857,0.71428 1.40625,10.875 1.40625,10.875 0,0 -12.48214,-3.75 -13.375,-5 -0.89285,-1.25 -12.5,-8.03125 -12.5,-8.03125 l -11.96875,-13.21875 z m 39.625,18.21875 9.125,3.9375 11.0625,6.25 0,8.375 -18.21875,-9.28125 c 0,0 -1.96875,-8.38839 -1.96875,-9.28125 z m -363.375,116.625 1.0625,0.6875 21.78125,17.875 10,4.625 c 0,0 -2.125,0.7366 -3.375,1.09375 C 97.6875,577.01339 91.96875,577 91.96875,577 l -7.6875,-4.09375 -8.5625,-8.75 -6.96875,-10.375 0.71875,-1.40625 z"
d3.json data_url, (points) ->
# hexagonal binning
radius = 26
apothem = Math.sqrt(3)/2 * radius
hexbin = d3.hexbin()
.size([width, height])
.radius(radius)
.x((d) -> projection([d.lng, d.lat])[0] )
.y((d) -> projection([d.lng, d.lat])[1] )
bins = _.chain(hexbin(points))
.forEach( (bin) ->
bin.classes_count = _.chain(categories)
.map( (klass) ->
count = bin.filter( (point) -> point.category is klass ).length
return {
count: count,
class: klass
}
)
.value()
)
.value()
max_tot = d3.max(bins, (bin) -> bin.length)
max = d3.max(bins, (bin) -> d3.max(bin.classes_count, (count) -> count.count) )
angle = 2*Math.PI / classes
# pie chart subplotting
subplots = zoom_group.selectAll('.subplot')
.data(bins)
subplots.enter().append('g')
.attr
class: 'subplot'
transform: (bin) -> "translate(#{bin.x},#{bin.y})"
max_dist = apothem / (1+Math.sin(angle/2))
dist_scale = d3.scale.sqrt()
.domain([0, max])
.range([0, max_dist])
petals = subplots.selectAll('.petal')
.data((bin) -> bin.classes_count)
petals.enter().append('circle')
.attr
class: 'petal'
cx: (d, i) -> dist_scale(d.count)*Math.cos(i*angle-Math.PI/2)
cy: (d, i) -> dist_scale(d.count)*Math.sin(i*angle-Math.PI/2)
r: (d) -> dist_scale(d.count)*Math.sin(angle/2)
fill: (count) -> colorify(count.class)
body {
margin: 0;
padding: 0;
}
svg {
background: white;
}
.petal {
stroke: white;
stroke-width: 1;
vector-effect: non-scaling-stroke;
}
.region {
stroke: white;
stroke-width: 3px;
fill: #DFDFDF;
}
.graticule {
fill: none;
stroke-width: 1px;
stroke: gray;
vector-effect: non-scaling-stroke;
}
.water {
fill: #BEDDF5;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="description" content="OpeNER - Places with categories (Paris, bubble binning)" />
<title>OpeNER - Places with categories (Paris, bubble 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://d3js.org/topojson.v1.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.min.js"></script>
</head>
<body>
<svg height="500" width="960"></svg>
<script src="index.js"></script>
</body>
</html>
(function() {
var categories, classes, colorify, data_url, geo_url, height, path_generator, projection, svg, width, zoom, zoom_group;
width = 960;
height = 500;
svg = d3.select('svg');
zoom_group = svg.append('g');
zoom = d3.behavior.zoom().scaleExtent([1, 4]).on('zoom', function() {
return zoom_group.attr('transform', "translate(" + (zoom.translate()) + ")scale(" + (zoom.scale()) + ")");
});
svg.call(zoom);
projection = d3.geo.azimuthalEqualArea().clipAngle(180 - 1e-3).scale(280000).rotate([-2.340087890624995, -48.858643406835796, 0]).translate([width / 2, height / 2]).precision(0.1);
path_generator = d3.geo.path().projection(projection);
categories = ['restaurant', 'attraction', 'poi', 'accommodation'];
classes = categories.length;
colorify = d3.scale.category10().domain(categories);
geo_url = 'http://wafi.iit.cnr.it/webvis/tmp/paris.topojson';
data_url = 'http://wafi.iit.cnr.it/webvis/tmp/paris.json';
d3.json(geo_url, function(geo) {
var regions;
regions = topojson.feature(geo, geo.objects['conseils-quartiers']);
zoom_group.selectAll('.region').data(regions.features).enter().append('path').attr('class', 'region').attr('d', path_generator);
zoom_group.append('path').attr({
"class": 'water',
transform: 'translate(45,-206) scale(1.1)',
d: "m 341.4375,154.84375 -27.78125,13.125 -27.78125,10.625 -8.09375,5.53125 -21.71875,16.6875 -10.09375,8.0625 -16.15625,10.625 -14.65625,6.0625 -19.6875,10.09375 -16.65625,8.59375 -8.59375,8.0625 -12.625,19.71875 -10.625,10.09375 -14.125,21.21875 -13.65625,18.1875 L 99,343.75 83.84375,361.9375 c 0,0 -13.146098,16.66211 -14.15625,19.1875 -1.010153,2.52538 -5.03125,17.65625 -5.03125,17.65625 l -9.09375,35.875 1.5,7.0625 2.53125,13.65625 -1,15.125 L 56.0625,485.15625 51,495.25 53.03125,515.96875 56.0625,520 l 5.0625,11.625 -1.53125,11.125 5.0625,11.09375 12.625,16.15625 11.625,8.59375 8.0625,1.53125 19.71875,-7.09375 13.625,-6.0625 7.5625,-4.03125 14.65625,-15.15625 12.625,-11.09375 13.65625,-21.21875 9.0625,-13.625 19.21875,-29.3125 L 217.1875,461.4375 235.375,443.75 246.46875,428.59375 249,419 l 9.09375,-10.09375 9.59375,-7.59375 12.125,-5.03125 29.28125,-2.03125 24.75,-0.5 32.84375,15.65625 13.625,6.5625 6.0625,0.5 7.59375,3.53125 11.09375,12.125 13.15625,8.09375 14.625,7.5625 21.71875,12.625 15.65625,15.15625 2.03125,6.5625 15.65625,17.1875 7.5625,10.09375 5.5625,7.5625 c 0,0 21.70859,24.76094 22.71875,26.78125 1.01015,2.0203 17.1875,20.71875 17.1875,20.71875 l 17.15625,17.15625 19.71875,10.625 13.625,6.0625 6.5625,6.5625 -1,12.625 -3.53125,22.71875 2.53125,13.125 9.09375,12.125 7.0625,10.625 4.03125,3.03125 6.5625,9.0625 8.59375,8.59375 4.03125,15.65625 -4.03125,17.1875 -22.71875,55.03125 24.75,-45.4375 5.03125,-22.71875 -0.5,-17.1875 -7.0625,-13.125 -8.59375,-11.625 -10.09375,-10.09375 -7.59375,-8.59375 -6.0625,-8.59375 -0.5,-8.5625 2.53125,-15.15625 2.53125,-17.1875 -2.53125,-4.03125 10.09375,1 15.15625,-2.53125 9.59375,1.53125 10.625,-1.53125 17.15625,3.5625 11.125,-2.03125 10.09375,-1.53125 18.6875,0.53125 -4.53125,-0.53125 -16.6875,-3.53125 -7.0625,1.53125 -8.59375,2.53125 -13.625,-2.53125 -12.625,2.53125 -10.09375,-3.03125 c 0,0 -11.63087,2 -14.15625,2 -2.52538,0 -12.625,-0.5 -12.625,-0.5 L 596.5,594.25 583.875,589.71875 566.6875,580.625 554.0625,569 540.4375,555.875 528.8125,540.71875 513.15625,520.53125 496,498.8125 485.875,487.1875 472.75,473.03125 457.09375,456.375 l -1.5,-5.5625 -1.03125,-6.5625 -14.65625,-7.5625 -13.625,-10.625 -19.6875,-7.0625 -13.125,-5.5625 -11.125,-1.5 L 367.1875,405.375 349,396.78125 l -16.15625,-9.09375 -13.125,0 -10.625,0 -25.25,1.5 -21.21875,7.59375 -9.59375,12.125 -12.625,16.65625 -23.71875,25.75 -24.25,36.375 -20.21875,30.3125 -13.625,19.6875 -9.09375,7.0625 -10.09375,11.625 -10.09375,7.0625 -11.125,6.0625 -12.125,1.03125 L 95.96875,567.5 82.3125,558.40625 75.25,554.34375 67.6875,543.75 l 0,-2.53125 0,-7.5625 -1.53125,-5.5625 -4.53125,-6.5625 -4.03125,-6.5625 -1.03125,-13.65625 3.03125,-10.09375 7.59375,-25.75 -2.53125,-12.625 -3.03125,-16.15625 3.53125,-25.28125 7.5625,-26.75 6.21875,-11.125 3.53125,-5.4375 18.53125,-6.6875 14.15625,-13.625 11.125,-18.1875 15.125,-23.21875 2.9375,-6.3125 -10.5,15.40625 L 123.25,327.5625 116.15625,344.25 99,358.375 l -14.96875,5.8125 1.8125,-2.375 8.84375,-10 18.96875,-20.6875 36.84375,-44.46875 0.625,-1.03125 -6.78125,14.4375 2.625,-3.8125 4.71875,-11.53125 11.96875,-19.78125 12.625,-13.625 27.78125,-15.65625 17.65625,-4.5625 L 241.4375,220 l 28.28125,-22.21875 20.1875,-14.15625 41.4375,-17.65625 10.09375,-11.125 z m 51.78125,262.6875 c 0,0 11.96875,4.11161 13.21875,4.46875 1.25,0.35714 16.25,6.78125 16.25,6.78125 l 6.96875,4.125 c 0.17857,0.71428 1.40625,10.875 1.40625,10.875 0,0 -12.48214,-3.75 -13.375,-5 -0.89285,-1.25 -12.5,-8.03125 -12.5,-8.03125 l -11.96875,-13.21875 z m 39.625,18.21875 9.125,3.9375 11.0625,6.25 0,8.375 -18.21875,-9.28125 c 0,0 -1.96875,-8.38839 -1.96875,-9.28125 z m -363.375,116.625 1.0625,0.6875 21.78125,17.875 10,4.625 c 0,0 -2.125,0.7366 -3.375,1.09375 C 97.6875,577.01339 91.96875,577 91.96875,577 l -7.6875,-4.09375 -8.5625,-8.75 -6.96875,-10.375 0.71875,-1.40625 z"
});
return d3.json(data_url, function(points) {
var angle, apothem, bins, dist_scale, hexbin, max, max_dist, max_tot, petals, radius, subplots;
radius = 26;
apothem = Math.sqrt(3) / 2 * radius;
hexbin = d3.hexbin().size([width, height]).radius(radius).x(function(d) {
return projection([d.lng, d.lat])[0];
}).y(function(d) {
return projection([d.lng, d.lat])[1];
});
bins = _.chain(hexbin(points)).forEach(function(bin) {
return bin.classes_count = _.chain(categories).map(function(klass) {
var count;
count = bin.filter(function(point) {
return point.category === klass;
}).length;
return {
count: count,
"class": klass
};
}).value();
}).value();
max_tot = d3.max(bins, function(bin) {
return bin.length;
});
max = d3.max(bins, function(bin) {
return d3.max(bin.classes_count, function(count) {
return count.count;
});
});
angle = 2 * Math.PI / classes;
subplots = zoom_group.selectAll('.subplot').data(bins);
subplots.enter().append('g').attr({
"class": 'subplot',
transform: function(bin) {
return "translate(" + bin.x + "," + bin.y + ")";
}
});
max_dist = apothem / (1 + Math.sin(angle / 2));
dist_scale = d3.scale.sqrt().domain([0, max]).range([0, max_dist]);
petals = subplots.selectAll('.petal').data(function(bin) {
return bin.classes_count;
});
return petals.enter().append('circle').attr({
"class": 'petal',
cx: function(d, i) {
return dist_scale(d.count) * Math.cos(i * angle - Math.PI / 2);
},
cy: function(d, i) {
return dist_scale(d.count) * Math.sin(i * angle - Math.PI / 2);
},
r: function(d) {
return dist_scale(d.count) * Math.sin(angle / 2);
},
fill: function(count) {
return colorify(count["class"]);
}
});
});
});
}).call(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment