Skip to content

Instantly share code, notes, and snippets.

@nitaku
Last active August 29, 2015 14:03
Show Gist options
  • Save nitaku/fc0db597d941956e6489 to your computer and use it in GitHub Desktop.
Save nitaku/fc0db597d941956e6489 to your computer and use it in GitHub Desktop.
OpeNER - Places with categories (polar chart binning)
# Setup
width = 960
height = 500
radius = 2
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()})")
# semantic zooming
zoom_group.selectAll('.place')
.attr('r', radius/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(160000)
.rotate([-10.277475, -42.789034, 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)
data_url = 'http://wafi.iit.cnr.it/webvis/tmp/elba_places.json'
geo_url = 'http://wafi.iit.cnr.it/webvis/italiastat/data/istat/2011/reg2011.topo.json'
d3.json geo_url, (geo) ->
regions = topojson.feature(geo, geo.objects.reg2011)
zoom_group.selectAll('.region')
.data(regions.features)
.enter().append('path')
.attr('class', 'region')
.attr('d', path_generator)
d3.json data_url, (points) ->
# hexagonal binning
radius = 22
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
# polar area chart subplotting
subplots = zoom_group.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, radius])
arc_generator = d3.svg.arc()
.innerRadius(0)
.outerRadius((count) -> radius_scale(count.count))
.startAngle((count, i) -> i*angle - angle/2)
.endAngle((count, i) -> i*angle + angle/2)
pies = subplots.selectAll('.pie')
.data((bin) -> bin.classes_count)
pies.enter().append('path')
.attr
class: 'pie'
d: arc_generator
fill: (count) -> colorify(count.class)
#zoom_group.selectAll('.place')
# .data(points)
# .enter().append('circle')
# .attr('class', 'place')
# .attr('r', radius)
# .attr('fill', (d) -> colorify(d.category) )
# .attr('transform', (d) ->
# p = projection([d.lng, d.lat])
# return "translate(#{p[0]},#{p[1]})"
# )
body {
margin: 0;
padding: 0;
}
svg {
background: #BEDDF5;
}
.region {
fill: white;
}
.graticule {
fill: none;
stroke-width: 1px;
stroke: gray;
vector-effect: non-scaling-stroke;
}
.pie {
stroke: white;
stroke-width: 0.5;
vector-effect: non-scaling-stroke;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="description" content="OpeNER - Places with categories (polar chart binning)" />
<title>OpeNER - Places with categories (polar chart 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, radius, svg, width, zoom, zoom_group;
width = 960;
height = 500;
radius = 2;
svg = d3.select('svg');
zoom_group = svg.append('g');
zoom = d3.behavior.zoom().scaleExtent([1, 4]).on('zoom', function() {
zoom_group.attr('transform', "translate(" + (zoom.translate()) + ")scale(" + (zoom.scale()) + ")");
return zoom_group.selectAll('.place').attr('r', radius / zoom.scale());
});
svg.call(zoom);
projection = d3.geo.azimuthalEqualArea().clipAngle(180 - 1e-3).scale(160000).rotate([-10.277475, -42.789034, 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);
data_url = 'http://wafi.iit.cnr.it/webvis/tmp/elba_places.json';
geo_url = 'http://wafi.iit.cnr.it/webvis/italiastat/data/istat/2011/reg2011.topo.json';
d3.json(geo_url, function(geo) {
var regions;
regions = topojson.feature(geo, geo.objects.reg2011);
zoom_group.selectAll('.region').data(regions.features).enter().append('path').attr('class', 'region').attr('d', path_generator);
return d3.json(data_url, function(points) {
var angle, apothem, arc_generator, bins, hexbin, max, max_tot, pies, radius_scale, subplots;
radius = 22;
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 + ")";
}
});
radius_scale = d3.scale.linear().domain([0, max]).range([0, radius]);
arc_generator = d3.svg.arc().innerRadius(0).outerRadius(function(count) {
return radius_scale(count.count);
}).startAngle(function(count, i) {
return i * angle - angle / 2;
}).endAngle(function(count, i) {
return i * angle + angle / 2;
});
pies = subplots.selectAll('.pie').data(function(bin) {
return bin.classes_count;
});
return pies.enter().append('path').attr({
"class": 'pie',
d: arc_generator,
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