Skip to content

Instantly share code, notes, and snippets.

@caged
Created August 26, 2011 15:50
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save caged/1173725 to your computer and use it in GitHub Desktop.
Save caged/1173725 to your computer and use it in GitHub Desktop.
d3.js experiment - Donuts, Bars and Crime.
#
# 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'
)
@leto
Copy link

leto commented Aug 26, 2011

spiffy!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment