Skip to content

Instantly share code, notes, and snippets.

@jraines
Created January 30, 2013 03:26
Show Gist options
  • Save jraines/4670344 to your computer and use it in GitHub Desktop.
Save jraines/4670344 to your computer and use it in GitHub Desktop.
# An attempt to do object oriented d3 graphing
#
# This graphs a swimmer's progress through a challenge set.
#
# x is the swim (1 of 5, 2 of 5, etc)
# y is their time in seconds
#
# radius of orange circles is the swimmers heart rate after 1 swim
#
# radius of red background circles is swimmer's heart rate after that swim
#
# data is in form: {hr: 155, seconds: 115}
# 'this' is the window; this makes the class available to js defined in other files
class @Graph
constructor: (config, json) ->
[@w, @h, @padding] = [config.width, config.height, config.padding]
@data = JSON.parse json
@data2 = JSON.parse json
#domain is number of seconds between 1:45 and 2:20
@y = d3.scale.linear().domain([105, 140]).range([@w - @padding, @padding])
#domain is index of swim
@x = d3.scale.linear().domain([-1, 5]).range([@padding, @h - @padding])
@yAxis =
d3.svg.axis()
.scale(@y)
.orient('left')
.tickFormat getTime
render: =>
@create_canvas()
@draw_axis()
@draw_best_time_line()
@create_circles()
@render_circles()
create_canvas: =>
@svg =
d3.select(config.element)
.append('svg')
.attr('width', @w)
.attr('height', @h)
#update the position of the info box to follow the mouse
.on('mousemove', mouseMoveFunction)
draw_axis: =>
@svg.append('g')
.attr('transform', "translate(#{@padding},0)")
.attr('class', 'yAxis')
.call @yAxis
#Draw a line across the graph showing the swimmer's best time in this event.
#Hard coding it in this case at 1:52
draw_best_time_line: =>
@svg.append('svg:line')
.attr('x1', 40)
.attr('x2', 500)
.attr('y1', @h - 112)
.attr('y2', @h - 112)
#weirdly, this comes out gray
.style('stroke', 'black')
create_circles: =>
@circles =
@svg.selectAll('.circle')
.data(@data)
.enter()
.append('circle')
@circles2 =
@svg.selectAll('.circle2')
.data(@data2)
.enter()
.append('circle')
render_circles: =>
@circles.attr('cx', (d, i) => @x(i))
.attr('cy', (d) => @y(parseInt(d.seconds)))
.attr('r', 0)
.attr('time', (d) -> getTime d.seconds)
.attr('hr', (d) -> d.hr)
.attr('fill', 'red')
.attr('class', 'point')
#d3 convention - outdent 2 spaces when changing the graph
.transition()
.duration(4000)
.attr('r', (d) -> (parseInt(d.hr) / 12) + 2)
@circles2.attr('cx', (d, i) => @x(i))
.attr('cy', (d) => @y(parseInt(d.seconds)))
.attr('r', 0)
.attr('time', (d) -> getTime d.seconds)
.attr('hr', (d) -> d.hr)
.attr('fill', 'orange')
.attr('class', 'circle2')
.transition()
.attr('r', parseInt(@data[0].hr) / 12)
#show and hide the info box tooltip
@circles2.on('mouseover', mouseOverFunction)
@circles2.on('mouseout', mouseOutFunction)
#event listeners for moving/displaying/hiding info tooltip
mouseOverFunction = ->
d3.select('.infobox').style('display', 'block')
d3.select('.infobox p').text d3.select(this).attr('hr')
mouseOutFunction = ->
d3.select('.infobox').style('display', 'none')
d3.select(this).attr('fill', 'orange')
mouseMoveFunction = ->
infobox = d3.select(".infobox")
infobox.style("left", d3.event.pageX + "px" )
infobox.style("top", d3.event.pageY + "px")
# format the time for displaying on the axis and info box
getTime = (seconds) ->
seconds = parseInt seconds
mins = Math.floor seconds/60
seconds = seconds % 60
seconds = seconds.toString()
seconds = ('0' + seconds) if seconds.length is 1
"#{mins}:#{seconds}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment