Created
August 26, 2011 15:50
-
-
Save caged/1173725 to your computer and use it in GitHub Desktop.
d3.js experiment - Donuts, Bars and Crime.
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
# | |
# CoffeeScript for http://dealloc.me/demos/crime/2011.html | |
# Copyright (c) 2011 Justin Palmer <http://github.com/Caged> | |
# LICENSE: http://www.opensource.org/licenses/mit-license.php | |
$ -> | |
hash = document.location.hash | |
year = if hash then hash.replace('#', '') else 2011 | |
[pt,pl,pb,pr] = [35, 20, 20, 20] | |
w = (900 - (pl + pr)) / 2 | |
h = w | |
r = (w - (pt * 2)) / 2 | |
cs = 0.65 | |
cr = r * cs | |
color = d3.scale.ordinal().range [ | |
'#ff5200', '#ff8f00', '#ffc200', | |
'#fff500', '#beea00', '#1dd100', | |
'#00ccca', '#0062cc', '#0b00cc', | |
'#7400cc', '#d40097', '#f50010', '#ff5200'] | |
arc = d3.svg.arc().innerRadius(r * .8).outerRadius r | |
donut = d3.layout.pie().sort(d3.descending).value((d) -> d.count) | |
d3.json "/data/top-neighborhoods-#{year}.json", (json) -> | |
# array offense name, {:nhoods, :hours} | |
data = json | |
containers = d3.select('#vis').selectAll('.offense') | |
.data(data) | |
.enter().append('div') | |
.attr('class', 'offense') | |
vis = containers.append('svg:svg') | |
.attr('width', w) | |
.attr('height', h) | |
.append('svg:g') | |
.attr('transform', "translate(#{(pt)},#{pt})") | |
# Primary Title | |
vis.append('svg:text') | |
.attr('class', 'ttl') | |
.text((d) -> d[0]) | |
.attr('dy', '-20px') | |
.attr('transform', "translate(#{(w - (pl + pr)) / 2})") | |
.attr('text-anchor', 'middle') | |
.attr('class', 'lbl') | |
# Arc Groups | |
arcs = vis.selectAll('.type') | |
.data((d) -> donut(d[1].nhoods.filter((v) -> v.count > 0))) | |
.enter().append('svg:g') | |
.attr('class', 'type') | |
.attr('transform', "translate(#{r},#{r})") | |
.on('mouseover', (d) -> | |
d3.select(this).attr('class', 'type on') | |
e = d3.select(this.ownerSVGElement) | |
e.select('.clbl').text(d.data.name) | |
e.select('.num').text(d.data.count)) | |
.on('mouseout', (d) -> | |
d3.select(this).attr('class', 'type') | |
e = d3.select(this.ownerSVGElement) | |
e.select('.clbl').text('total') | |
e.select('.num').text((d) -> d3.sum(d[1].nhoods, (nh) -> nh.count ))) | |
# Draw arcs | |
arcs.append('svg:path') | |
.attr('d', (d) -> arc(d)) | |
.style('fill', (d, i) -> d3.rgb(color(i)).darker(1)) | |
# Add arc data labels | |
arcs.append('svg:text') | |
.attr('transform', (d) -> "translate(#{arc.centroid(d)})") | |
.attr('text-anchor', 'middle') | |
.attr('dy', '.35em') | |
.attr('fill', '#fff') | |
.text((d) -> d.value) | |
# Add center group | |
lbls = vis.append('svg:g') | |
.attr('transform', "translate(#{(w - (pt * 2)) / 2}, #{(h - (pt * 2)) / 2})") | |
# Add center text | |
lbls.append('svg:text') | |
.attr('dy', -5) | |
.attr('text-anchor', 'middle') | |
.attr('class', 'num') | |
.text((d) -> d3.sum(d[1].nhoods, (nh) -> nh.count )) | |
lbls.append('svg:text') | |
.attr('dy', 10) | |
.attr('text-anchor', 'middle') | |
.attr('class', 'clbl') | |
.text('total') | |
# Visual fluff circle | |
vis.append('svg:circle') | |
.attr('cx', (w - (pt * 2)) / 2) | |
.attr('cy', (h - (pt * 2)) / 2) | |
.attr('r', r * 0.65) | |
.attr('class', 'ic') | |
# Rotated labels | |
arcs.append('svg:text') | |
.attr('transform', (d, i) -> | |
ang = ((d.startAngle + d.endAngle) / 2) * 180 / Math.PI | |
"translate(#{arc.centroid(d)}) rotate(#{ang})") | |
.attr('dy', 35) | |
.attr('fill', '#333') | |
.attr('text-anchor', 'middle') | |
.attr('class', 'nhlbl') | |
.text((d) -> d.data.name[0..1].toLowerCase()) | |
################################################################################################ | |
# Bar Chart | |
################################################################################################ | |
[pt, pl, pr, pb] = [30, 20, 20, 60] # padding | |
w = w - (pl + pr) | |
h = 150 - (pt + pb) | |
yscale = (data) -> | |
d3.scale.linear().domain([0, d3.max(data)]).range([h, 0]) | |
yscalefor = (e) -> | |
hours = e.parentNode.parentNode.__data__[1].hours.map((h) -> h.count) | |
yscale(hours) | |
x = d3.scale.ordinal().domain(d3.range(24)).rangeRoundBands([0, w], .2) | |
x2 = d3.scale.linear().domain([0, 23]).range [0, w] | |
line = d3.svg.line().x((d,i) -> x2(i)).y((d) -> yscalefor(this)(d)).interpolate('monotone') #.tension(0.8) | |
vis = containers.append('svg:svg') | |
.attr('width', w + pr + pl) | |
.attr('height', h + pt + pb) | |
.append('svg:g') | |
.attr('transform', "translate(#{pl},#{pt})") | |
vis.append('svg:text') | |
.text('Time of Day') | |
.attr('transform', "translate(#{w / 2},#{h + 30})") | |
.attr('text-anchor', 'middle') | |
vis.append('svg:line') | |
.attr('y1', h) | |
.attr('y2', h) | |
.attr('x1', 0) | |
.attr('x2', w) | |
.attr('class', 'ytick') | |
bars = vis.selectAll('g.bar') | |
.data((d) -> d[1].hours.map((h) -> h.count)) | |
.enter().append('svg:g') | |
.attr('transform', (d, i) -> "translate(#{x(i)},0)") | |
bars.append('svg:rect') | |
.attr('width', x.rangeBand()) | |
.attr('height', (d) -> h - yscalefor(this)(d)) | |
.attr('y', (d) -> yscalefor(this)(d)) | |
.attr('class', (d, i) -> if i in [6..17] then 'day bar' else 'bar') | |
.append('svg:title') | |
.text((d) -> d) | |
# vis.selectAll('path.line') | |
# .data((d) -> [d[1].hours.map((h) -> h.count)]) | |
# .enter().append("svg:path") | |
# .attr("d", line) | |
# .attr('class', 'hourpath') | |
bars.append('svg:text') | |
.attr('x', x.rangeBand() / 2) | |
.attr('text-anchor', 'middle') | |
.attr('y', h + 10) | |
.attr('class', 'xlbl') | |
.text((d, i) -> h = i % 12; if h == 0 then 12 else h) | |
.style('visibility', (d, i) -> if i % 3 == 0 && i != 0 then 'visible' else 'hidden') | |
bars.append('svg:text') | |
.attr('dx', 15) | |
.attr('dy', 9) | |
.attr('text-anchor', 'middle') | |
.attr('class', 'max') | |
.text((d, i) -> d) | |
.attr('transform', "rotate(-90)") | |
.style('visibility', (d, i) -> | |
hours = this.parentNode.parentNode.__data__[1].hours.map((h) -> h.count) | |
if d3.max(hours) == d then 'visible' else 'hidden' | |
) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
spiffy!