| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499 | 
							- class Morris.Grid extends Morris.EventEmitter
 
-   # A generic pair of axes for line/area/bar charts.
 
-   #
 
-   # Draws grid lines and axis labels.
 
-   #
 
-   constructor: (options) ->
 
-     # find the container to draw the graph in
 
-     if typeof options.element is 'string'
 
-       @el = $ document.getElementById(options.element)
 
-     else
 
-       @el = $ options.element
 
-     if not @el? or @el.length == 0
 
-       throw new Error("Graph container element not found")
 
-     if @el.css('position') == 'static'
 
-       @el.css('position', 'relative')
 
-     @options = $.extend {}, @gridDefaults, (@defaults || {}), options
 
-     # backwards compatibility for units -> postUnits
 
-     if typeof @options.units is 'string'
 
-       @options.postUnits = options.units
 
-     # the raphael drawing instance
 
-     @raphael = new Raphael(@el[0])
 
-     # some redraw stuff
 
-     @elementWidth = null
 
-     @elementHeight = null
 
-     @dirty = false
 
-     # range selection
 
-     @selectFrom = null
 
-     # more stuff
 
-     @init() if @init
 
-     # load data
 
-     @setData @options.data
 
-     # hover
 
-     @el.bind 'mousemove', (evt) =>
 
-       offset = @el.offset()
 
-       x = evt.pageX - offset.left
 
-       if @selectFrom
 
-         left = @data[@hitTest(Math.min(x, @selectFrom))]._x
 
-         right = @data[@hitTest(Math.max(x, @selectFrom))]._x
 
-         width = right - left
 
-         @selectionRect.attr({ x: left, width: width })
 
-       else
 
-         @fire 'hovermove', x, evt.pageY - offset.top
 
-     @el.bind 'mouseleave', (evt) =>
 
-       if @selectFrom
 
-         @selectionRect.hide()
 
-         @selectFrom = null
 
-       @fire 'hoverout'
 
-     @el.bind 'touchstart touchmove touchend', (evt) =>
 
-       touch = evt.originalEvent.touches[0] or evt.originalEvent.changedTouches[0]
 
-       offset = @el.offset()
 
-       @fire 'hovermove', touch.pageX - offset.left, touch.pageY - offset.top
 
-     @el.bind 'click', (evt) =>
 
-       offset = @el.offset()
 
-       @fire 'gridclick', evt.pageX - offset.left, evt.pageY - offset.top
 
-     if @options.rangeSelect
 
-       @selectionRect = @raphael.rect(0, 0, 0, @el.innerHeight())
 
-         .attr({ fill: @options.rangeSelectColor, stroke: false })
 
-         .toBack()
 
-         .hide()
 
-       @el.bind 'mousedown', (evt) =>
 
-         offset = @el.offset()
 
-         @startRange evt.pageX - offset.left
 
-       @el.bind 'mouseup', (evt) =>
 
-         offset = @el.offset()
 
-         @endRange evt.pageX - offset.left
 
-         @fire 'hovermove', evt.pageX - offset.left, evt.pageY - offset.top
 
-     if @options.resize
 
-       $(window).bind 'resize', (evt) =>
 
-         if @timeoutId?
 
-           window.clearTimeout @timeoutId
 
-         @timeoutId = window.setTimeout @resizeHandler, 100
 
-     # Disable tap highlight on iOS.
 
-     @el.css('-webkit-tap-highlight-color', 'rgba(0,0,0,0)')
 
-     @postInit() if @postInit
 
-   # Default options
 
-   #
 
-   gridDefaults:
 
-     dateFormat: null
 
-     axes: true
 
-     grid: true
 
-     gridLineColor: '#aaa'
 
-     gridStrokeWidth: 0.5
 
-     gridTextColor: '#888'
 
-     gridTextSize: 12
 
-     gridTextFamily: 'sans-serif'
 
-     gridTextWeight: 'normal'
 
-     hideHover: false
 
-     yLabelFormat: null
 
-     xLabelAngle: 0
 
-     numLines: 5
 
-     padding: 25
 
-     parseTime: true
 
-     postUnits: ''
 
-     preUnits: ''
 
-     ymax: 'auto'
 
-     ymin: 'auto 0'
 
-     goals: []
 
-     goalStrokeWidth: 1.0
 
-     goalLineColors: [
 
-       '#666633'
 
-       '#999966'
 
-       '#cc6666'
 
-       '#663333'
 
-     ]
 
-     events: []
 
-     eventStrokeWidth: 1.0
 
-     eventLineColors: [
 
-       '#005a04'
 
-       '#ccffbb'
 
-       '#3a5f0b'
 
-       '#005502'
 
-     ]
 
-     rangeSelect: null
 
-     rangeSelectColor: '#eef'
 
-     resize: false
 
-   # Update the data series and redraw the chart.
 
-   #
 
-   setData: (data, redraw = true) ->
 
-     @options.data = data
 
-     if !data? or data.length == 0
 
-       @data = []
 
-       @raphael.clear()
 
-       @hover.hide() if @hover?
 
-       return
 
-     ymax = if @cumulative then 0 else null
 
-     ymin = if @cumulative then 0 else null
 
-     if @options.goals.length > 0
 
-       minGoal = Math.min @options.goals...
 
-       maxGoal = Math.max @options.goals...
 
-       ymin = if ymin? then Math.min(ymin, minGoal) else minGoal
 
-       ymax = if ymax? then Math.max(ymax, maxGoal) else maxGoal
 
-     @data = for row, index in data
 
-       ret = {src: row}
 
-       ret.label = row[@options.xkey]
 
-       if @options.parseTime
 
-         ret.x = Morris.parseDate(ret.label)
 
-         if @options.dateFormat
 
-           ret.label = @options.dateFormat ret.x
 
-         else if typeof ret.label is 'number'
 
-           ret.label = new Date(ret.label).toString()
 
-       else
 
-         ret.x = index
 
-         if @options.xLabelFormat
 
-           ret.label = @options.xLabelFormat ret
 
-       total = 0
 
-       ret.y = for ykey, idx in @options.ykeys
 
-         yval = row[ykey]
 
-         yval = parseFloat(yval) if typeof yval is 'string'
 
-         yval = null if yval? and typeof yval isnt 'number'
 
-         if yval?
 
-           if @cumulative
 
-             total += yval
 
-           else
 
-             if ymax?
 
-               ymax = Math.max(yval, ymax)
 
-               ymin = Math.min(yval, ymin)
 
-             else
 
-               ymax = ymin = yval
 
-         if @cumulative and total?
 
-           ymax = Math.max(total, ymax)
 
-           ymin = Math.min(total, ymin)
 
-         yval
 
-       ret
 
-     if @options.parseTime
 
-       @data = @data.sort (a, b) -> (a.x > b.x) - (b.x > a.x)
 
-     # calculate horizontal range of the graph
 
-     @xmin = @data[0].x
 
-     @xmax = @data[@data.length - 1].x
 
-     @events = []
 
-     if @options.events.length > 0
 
-       if @options.parseTime
 
-         @events = (Morris.parseDate(e) for e in @options.events)
 
-       else
 
-         @events = @options.events
 
-       @xmax = Math.max(@xmax, Math.max(@events...))
 
-       @xmin = Math.min(@xmin, Math.min(@events...))
 
-     if @xmin is @xmax
 
-       @xmin -= 1
 
-       @xmax += 1
 
-     @ymin = @yboundary('min', ymin)
 
-     @ymax = @yboundary('max', ymax)
 
-     if @ymin is @ymax
 
-       @ymin -= 1 if ymin
 
-       @ymax += 1
 
-     if @options.axes in [true, 'both', 'y'] or @options.grid is true
 
-       if (@options.ymax == @gridDefaults.ymax and
 
-           @options.ymin == @gridDefaults.ymin)
 
-         # calculate 'magic' grid placement
 
-         @grid = @autoGridLines(@ymin, @ymax, @options.numLines)
 
-         @ymin = Math.min(@ymin, @grid[0])
 
-         @ymax = Math.max(@ymax, @grid[@grid.length - 1])
 
-       else
 
-         step = (@ymax - @ymin) / (@options.numLines - 1)
 
-         @grid = (y for y in [@ymin..@ymax] by step)
 
-     @dirty = true
 
-     @redraw() if redraw
 
-   yboundary: (boundaryType, currentValue) ->
 
-     boundaryOption = @options["y#{boundaryType}"]
 
-     if typeof boundaryOption is 'string'
 
-       if boundaryOption[0..3] is 'auto'
 
-         if boundaryOption.length > 5
 
-           suggestedValue = parseInt(boundaryOption[5..], 10)
 
-           return suggestedValue unless currentValue?
 
-           Math[boundaryType](currentValue, suggestedValue)
 
-         else
 
-           if currentValue? then currentValue else 0
 
-       else
 
-         parseInt(boundaryOption, 10)
 
-     else
 
-       boundaryOption
 
-   autoGridLines: (ymin, ymax, nlines) ->
 
-     span = ymax - ymin
 
-     ymag = Math.floor(Math.log(span) / Math.log(10))
 
-     unit = Math.pow(10, ymag)
 
-     # calculate initial grid min and max values
 
-     gmin = Math.floor(ymin / unit) * unit
 
-     gmax = Math.ceil(ymax / unit) * unit
 
-     step = (gmax - gmin) / (nlines - 1)
 
-     if unit == 1 and step > 1 and Math.ceil(step) != step
 
-       step = Math.ceil(step)
 
-       gmax = gmin + step * (nlines - 1)
 
-     # ensure zero is plotted where the range includes zero
 
-     if gmin < 0 and gmax > 0
 
-       gmin = Math.floor(ymin / step) * step
 
-       gmax = Math.ceil(ymax / step) * step
 
-     # special case for decimal numbers
 
-     if step < 1
 
-       smag = Math.floor(Math.log(step) / Math.log(10))
 
-       grid = for y in [gmin..gmax] by step
 
-         parseFloat(y.toFixed(1 - smag))
 
-     else
 
-       grid = (y for y in [gmin..gmax] by step)
 
-     grid
 
-   _calc: ->
 
-     w = @el.width()
 
-     h = @el.height()
 
-     if @elementWidth != w or @elementHeight != h or @dirty
 
-       @elementWidth = w
 
-       @elementHeight = h
 
-       @dirty = false
 
-       # recalculate grid dimensions
 
-       @left = @options.padding
 
-       @right = @elementWidth - @options.padding
 
-       @top = @options.padding
 
-       @bottom = @elementHeight - @options.padding
 
-       if @options.axes in [true, 'both', 'y']
 
-         yLabelWidths = for gridLine in @grid
 
-           @measureText(@yAxisFormat(gridLine)).width
 
-         @left += Math.max(yLabelWidths...)
 
-       if @options.axes in [true, 'both', 'x']
 
-         bottomOffsets = for i in [0...@data.length]
 
-           @measureText(@data[i].text, -@options.xLabelAngle).height
 
-         @bottom -= Math.max(bottomOffsets...)
 
-       @width = Math.max(1, @right - @left)
 
-       @height = Math.max(1, @bottom - @top)
 
-       @dx = @width / (@xmax - @xmin)
 
-       @dy = @height / (@ymax - @ymin)
 
-       @calc() if @calc
 
-   # Quick translation helpers
 
-   #
 
-   transY: (y) -> @bottom - (y - @ymin) * @dy
 
-   transX: (x) ->
 
-     if @data.length == 1
 
-       (@left + @right) / 2
 
-     else
 
-       @left + (x - @xmin) * @dx
 
-   # Draw it!
 
-   #
 
-   # If you need to re-size your charts, call this method after changing the
 
-   # size of the container element.
 
-   redraw: ->
 
-     @raphael.clear()
 
-     @_calc()
 
-     @drawGrid()
 
-     @drawGoals()
 
-     @drawEvents()
 
-     @draw() if @draw
 
-   # @private
 
-   #
 
-   measureText: (text, angle = 0) ->
 
-     tt = @raphael.text(100, 100, text)
 
-       .attr('font-size', @options.gridTextSize)
 
-       .attr('font-family', @options.gridTextFamily)
 
-       .attr('font-weight', @options.gridTextWeight)
 
-       .rotate(angle)
 
-     ret = tt.getBBox()
 
-     tt.remove()
 
-     ret
 
-   # @private
 
-   #
 
-   yAxisFormat: (label) -> @yLabelFormat(label)
 
-   # @private
 
-   #
 
-   yLabelFormat: (label) ->
 
-     if typeof @options.yLabelFormat is 'function'
 
-       @options.yLabelFormat(label)
 
-     else
 
-       "#{@options.preUnits}#{Morris.commas(label)}#{@options.postUnits}"
 
-   # draw y axis labels, horizontal lines
 
-   #
 
-   drawGrid: ->
 
-     return if @options.grid is false and @options.axes not in [true, 'both', 'y']
 
-     for lineY in @grid
 
-       y = @transY(lineY)
 
-       if @options.axes in [true, 'both', 'y']
 
-         @drawYAxisLabel(@left - @options.padding / 2, y, @yAxisFormat(lineY))
 
-       if @options.grid
 
-         @drawGridLine("M#{@left},#{y}H#{@left + @width}")
 
-   # draw goals horizontal lines
 
-   #
 
-   drawGoals: ->
 
-     for goal, i in @options.goals
 
-       color = @options.goalLineColors[i % @options.goalLineColors.length]
 
-       @drawGoal(goal, color)
 
-   # draw events vertical lines
 
-   drawEvents: ->
 
-     for event, i in @events
 
-       color = @options.eventLineColors[i % @options.eventLineColors.length]
 
-       @drawEvent(event, color)
 
-   drawGoal: (goal, color) ->
 
-     @raphael.path("M#{@left},#{@transY(goal)}H#{@right}")
 
-       .attr('stroke', color)
 
-       .attr('stroke-width', @options.goalStrokeWidth)
 
-   drawEvent: (event, color) ->
 
-     @raphael.path("M#{@transX(event)},#{@bottom}V#{@top}")
 
-       .attr('stroke', color)
 
-       .attr('stroke-width', @options.eventStrokeWidth)
 
-   drawYAxisLabel: (xPos, yPos, text) ->
 
-     @raphael.text(xPos, yPos, text)
 
-       .attr('font-size', @options.gridTextSize)
 
-       .attr('font-family', @options.gridTextFamily)
 
-       .attr('font-weight', @options.gridTextWeight)
 
-       .attr('fill', @options.gridTextColor)
 
-       .attr('text-anchor', 'end')
 
-   drawGridLine: (path) ->
 
-     @raphael.path(path)
 
-       .attr('stroke', @options.gridLineColor)
 
-       .attr('stroke-width', @options.gridStrokeWidth)
 
-   # Range selection
 
-   #
 
-   startRange: (x) ->
 
-     @hover.hide()
 
-     @selectFrom = x
 
-     @selectionRect.attr({ x: x, width: 0 }).show()
 
-   endRange: (x) ->
 
-     if @selectFrom
 
-       start = Math.min(@selectFrom, x)
 
-       end = Math.max(@selectFrom, x)
 
-       @options.rangeSelect.call @el,
 
-         start: @data[@hitTest(start)].x
 
-         end: @data[@hitTest(end)].x
 
-       @selectFrom = null
 
-   resizeHandler: =>
 
-     @timeoutId = null
 
-     @raphael.setSize @el.width(), @el.height()
 
-     @redraw()
 
- # Parse a date into a javascript timestamp
 
- #
 
- #
 
- Morris.parseDate = (date) ->
 
-   if typeof date is 'number'
 
-     return date
 
-   m = date.match /^(\d+) Q(\d)$/
 
-   n = date.match /^(\d+)-(\d+)$/
 
-   o = date.match /^(\d+)-(\d+)-(\d+)$/
 
-   p = date.match /^(\d+) W(\d+)$/
 
-   q = date.match /^(\d+)-(\d+)-(\d+)[ T](\d+):(\d+)(Z|([+-])(\d\d):?(\d\d))?$/
 
-   r = date.match /^(\d+)-(\d+)-(\d+)[ T](\d+):(\d+):(\d+(\.\d+)?)(Z|([+-])(\d\d):?(\d\d))?$/
 
-   if m
 
-     new Date(
 
-       parseInt(m[1], 10),
 
-       parseInt(m[2], 10) * 3 - 1,
 
-       1).getTime()
 
-   else if n
 
-     new Date(
 
-       parseInt(n[1], 10),
 
-       parseInt(n[2], 10) - 1,
 
-       1).getTime()
 
-   else if o
 
-     new Date(
 
-       parseInt(o[1], 10),
 
-       parseInt(o[2], 10) - 1,
 
-       parseInt(o[3], 10)).getTime()
 
-   else if p
 
-     # calculate number of weeks in year given
 
-     ret = new Date(parseInt(p[1], 10), 0, 1);
 
-     # first thursday in year (ISO 8601 standard)
 
-     if ret.getDay() isnt 4
 
-       ret.setMonth(0, 1 + ((4 - ret.getDay()) + 7) % 7);
 
-     # add weeks
 
-     ret.getTime() + parseInt(p[2], 10) * 604800000
 
-   else if q
 
-     if not q[6]
 
-       # no timezone info, use local
 
-       new Date(
 
-         parseInt(q[1], 10),
 
-         parseInt(q[2], 10) - 1,
 
-         parseInt(q[3], 10),
 
-         parseInt(q[4], 10),
 
-         parseInt(q[5], 10)).getTime()
 
-     else
 
-       # timezone info supplied, use UTC
 
-       offsetmins = 0
 
-       if q[6] != 'Z'
 
-         offsetmins = parseInt(q[8], 10) * 60 + parseInt(q[9], 10)
 
-         offsetmins = 0 - offsetmins if q[7] == '+'
 
-       Date.UTC(
 
-         parseInt(q[1], 10),
 
-         parseInt(q[2], 10) - 1,
 
-         parseInt(q[3], 10),
 
-         parseInt(q[4], 10),
 
-         parseInt(q[5], 10) + offsetmins)
 
-   else if r
 
-     secs = parseFloat(r[6])
 
-     isecs = Math.floor(secs)
 
-     msecs = Math.round((secs - isecs) * 1000)
 
-     if not r[8]
 
-       # no timezone info, use local
 
-       new Date(
 
-         parseInt(r[1], 10),
 
-         parseInt(r[2], 10) - 1,
 
-         parseInt(r[3], 10),
 
-         parseInt(r[4], 10),
 
-         parseInt(r[5], 10),
 
-         isecs,
 
-         msecs).getTime()
 
-     else
 
-       # timezone info supplied, use UTC
 
-       offsetmins = 0
 
-       if r[8] != 'Z'
 
-         offsetmins = parseInt(r[10], 10) * 60 + parseInt(r[11], 10)
 
-         offsetmins = 0 - offsetmins if r[9] == '+'
 
-       Date.UTC(
 
-         parseInt(r[1], 10),
 
-         parseInt(r[2], 10) - 1,
 
-         parseInt(r[3], 10),
 
-         parseInt(r[4], 10),
 
-         parseInt(r[5], 10) + offsetmins,
 
-         isecs,
 
-         msecs)
 
-   else
 
-     new Date(parseInt(date, 10), 0, 1).getTime()
 
 
  |