| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405 | class Morris.Line extends Morris.Grid  # Initialise the graph.  #  constructor: (options) ->    return new Morris.Line(options) unless (@ instanceof Morris.Line)    super(options)  init: ->    # Some instance variables for later    if @options.hideHover isnt 'always'      @hover = new Morris.Hover(parent: @el)      @on('hovermove', @onHoverMove)      @on('hoverout', @onHoverOut)      @on('gridclick', @onGridClick)  # Default configuration  #  defaults:    lineWidth: 3    pointSize: 4    lineColors: [      '#0b62a4'      '#7A92A3'      '#4da74d'      '#afd8f8'      '#edc240'      '#cb4b4b'      '#9440ed'    ]    pointStrokeWidths: [1]    pointStrokeColors: ['#ffffff']    pointFillColors: []    smooth: true    xLabels: 'auto'    xLabelFormat: null    xLabelMargin: 24    hideHover: false  # Do any size-related calculations  #  # @private  calc: ->    @calcPoints()    @generatePaths()  # calculate series data point coordinates  #  # @private  calcPoints: ->    for row in @data      row._x = @transX(row.x)      row._y = for y in row.y        if y? then @transY(y) else y      row._ymax = Math.min [@bottom].concat(y for y in row._y when y?)...  # hit test - returns the index of the row at the given x-coordinate  #  hitTest: (x) ->    return null if @data.length == 0    # TODO better search algo    for r, index in @data.slice(1)      break if x < (r._x + @data[index]._x) / 2    index  # click on grid event handler  #  # @private  onGridClick: (x, y) =>    index = @hitTest(x)    @fire 'click', index, @data[index].src, x, y  # hover movement event handler  #  # @private  onHoverMove: (x, y) =>    index = @hitTest(x)    @displayHoverForRow(index)  # hover out event handler  #  # @private  onHoverOut: =>    if @options.hideHover isnt false      @displayHoverForRow(null)  # display a hover popup over the given row  #  # @private  displayHoverForRow: (index) ->    if index?      @hover.update(@hoverContentForRow(index)...)      @hilight(index)    else      @hover.hide()      @hilight()  # hover content for a point  #  # @private  hoverContentForRow: (index) ->    row = @data[index]    content = "<div class='morris-hover-row-label'>#{row.label}</div>"    for y, j in row.y      content += """        <div class='morris-hover-point' style='color: #{@colorFor(row, j, 'label')}'>          #{@options.labels[j]}:          #{@yLabelFormat(y)}        </div>      """    if typeof @options.hoverCallback is 'function'      content = @options.hoverCallback(index, @options, content, row.src)    [content, row._x, row._ymax]  # generate paths for series lines  #  # @private  generatePaths: ->    @paths = for i in [0...@options.ykeys.length]      smooth = if typeof @options.smooth is "boolean" then @options.smooth else @options.ykeys[i] in @options.smooth      coords = ({x: r._x, y: r._y[i]} for r in @data when r._y[i] isnt undefined)      if coords.length > 1        Morris.Line.createPath coords, smooth, @bottom      else        null  # Draws the line chart.  #  draw: ->    @drawXAxis() if @options.axes in [true, 'both', 'x']    @drawSeries()    if @options.hideHover is false      @displayHoverForRow(@data.length - 1)  # draw the x-axis labels  #  # @private  drawXAxis: ->    # draw x axis labels    ypos = @bottom + @options.padding / 2    prevLabelMargin = null    prevAngleMargin = null    drawLabel = (labelText, xpos) =>      label = @drawXAxisLabel(@transX(xpos), ypos, labelText)      textBox = label.getBBox()      label.transform("r#{-@options.xLabelAngle}")      labelBox = label.getBBox()      label.transform("t0,#{labelBox.height / 2}...")      if @options.xLabelAngle != 0        offset = -0.5 * textBox.width *          Math.cos(@options.xLabelAngle * Math.PI / 180.0)        label.transform("t#{offset},0...")      # try to avoid overlaps      labelBox = label.getBBox()      if (not prevLabelMargin? or          prevLabelMargin >= labelBox.x + labelBox.width or          prevAngleMargin? and prevAngleMargin >= labelBox.x) and         labelBox.x >= 0 and (labelBox.x + labelBox.width) < @el.width()        if @options.xLabelAngle != 0          margin = 1.25 * @options.gridTextSize /            Math.sin(@options.xLabelAngle * Math.PI / 180.0)          prevAngleMargin = labelBox.x - margin        prevLabelMargin = labelBox.x - @options.xLabelMargin      else        label.remove()    if @options.parseTime      if @data.length == 1 and @options.xLabels == 'auto'        # where there's only one value in the series, we can't make a        # sensible guess for an x labelling scheme, so just use the original        # column label        labels = [[@data[0].label, @data[0].x]]      else        labels = Morris.labelSeries(@xmin, @xmax, @width, @options.xLabels, @options.xLabelFormat)    else      labels = ([row.label, row.x] for row in @data)    labels.reverse()    for l in labels      drawLabel(l[0], l[1])  # draw the data series  #  # @private  drawSeries: ->    @seriesPoints = []    for i in [@options.ykeys.length-1..0]      @_drawLineFor i    for i in [@options.ykeys.length-1..0]      @_drawPointFor i  _drawPointFor: (index) ->    @seriesPoints[index] = []    for row in @data      circle = null      if row._y[index]?        circle = @drawLinePoint(row._x, row._y[index], @colorFor(row, index, 'point'), index)      @seriesPoints[index].push(circle)  _drawLineFor: (index) ->    path = @paths[index]    if path isnt null      @drawLinePath path, @colorFor(null, index, 'line'), index  # create a path for a data series  #  # @private  @createPath: (coords, smooth, bottom) ->    path = ""    grads = Morris.Line.gradients(coords) if smooth    prevCoord = {y: null}    for coord, i in coords      if coord.y?        if prevCoord.y?          if smooth            g = grads[i]            lg = grads[i - 1]            ix = (coord.x - prevCoord.x) / 4            x1 = prevCoord.x + ix            y1 = Math.min(bottom, prevCoord.y + ix * lg)            x2 = coord.x - ix            y2 = Math.min(bottom, coord.y - ix * g)            path += "C#{x1},#{y1},#{x2},#{y2},#{coord.x},#{coord.y}"          else            path += "L#{coord.x},#{coord.y}"        else          if not smooth or grads[i]?            path += "M#{coord.x},#{coord.y}"      prevCoord = coord    return path  # calculate a gradient at each point for a series of points  #  # @private  @gradients: (coords) ->    grad = (a, b) -> (a.y - b.y) / (a.x - b.x)    for coord, i in coords      if coord.y?        nextCoord = coords[i + 1] or {y: null}        prevCoord = coords[i - 1] or {y: null}        if prevCoord.y? and nextCoord.y?          grad(prevCoord, nextCoord)        else if prevCoord.y?          grad(prevCoord, coord)        else if nextCoord.y?          grad(coord, nextCoord)        else          null      else        null  # @private  hilight: (index) =>    if @prevHilight isnt null and @prevHilight isnt index      for i in [0..@seriesPoints.length-1]        if @seriesPoints[i][@prevHilight]          @seriesPoints[i][@prevHilight].animate @pointShrinkSeries(i)    if index isnt null and @prevHilight isnt index      for i in [0..@seriesPoints.length-1]        if @seriesPoints[i][index]          @seriesPoints[i][index].animate @pointGrowSeries(i)    @prevHilight = index  colorFor: (row, sidx, type) ->    if typeof @options.lineColors is 'function'      @options.lineColors.call(@, row, sidx, type)    else if type is 'point'      @options.pointFillColors[sidx % @options.pointFillColors.length] || @options.lineColors[sidx % @options.lineColors.length]    else      @options.lineColors[sidx % @options.lineColors.length]  drawXAxisLabel: (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)  drawLinePath: (path, lineColor, lineIndex) ->    @raphael.path(path)      .attr('stroke', lineColor)      .attr('stroke-width', @lineWidthForSeries(lineIndex))  drawLinePoint: (xPos, yPos, pointColor, lineIndex) ->    @raphael.circle(xPos, yPos, @pointSizeForSeries(lineIndex))      .attr('fill', pointColor)      .attr('stroke-width', @pointStrokeWidthForSeries(lineIndex))      .attr('stroke', @pointStrokeColorForSeries(lineIndex))  # @private  pointStrokeWidthForSeries: (index) ->    @options.pointStrokeWidths[index % @options.pointStrokeWidths.length]  # @private  pointStrokeColorForSeries: (index) ->    @options.pointStrokeColors[index % @options.pointStrokeColors.length]  # @private  lineWidthForSeries: (index) ->    if (@options.lineWidth instanceof Array)      @options.lineWidth[index % @options.lineWidth.length]    else      @options.lineWidth  # @private  pointSizeForSeries: (index) ->    if (@options.pointSize instanceof Array)      @options.pointSize[index % @options.pointSize.length]    else      @options.pointSize  # @private  pointGrowSeries: (index) ->    Raphael.animation r: @pointSizeForSeries(index) + 3, 25, 'linear'  # @private  pointShrinkSeries: (index) ->    Raphael.animation r: @pointSizeForSeries(index), 25, 'linear'# generate a series of label, timestamp pairs for x-axis labels## @privateMorris.labelSeries = (dmin, dmax, pxwidth, specName, xLabelFormat) ->  ddensity = 200 * (dmax - dmin) / pxwidth # seconds per `margin` pixels  d0 = new Date(dmin)  spec = Morris.LABEL_SPECS[specName]  # if the spec doesn't exist, search for the closest one in the list  if spec is undefined    for name in Morris.AUTO_LABEL_ORDER      s = Morris.LABEL_SPECS[name]      if ddensity >= s.span        spec = s        break  # if we run out of options, use second-intervals  if spec is undefined    spec = Morris.LABEL_SPECS["second"]  # check if there's a user-defined formatting function  if xLabelFormat    spec = $.extend({}, spec, {fmt: xLabelFormat})  # calculate labels  d = spec.start(d0)  ret = []  while  (t = d.getTime()) <= dmax    if t >= dmin      ret.push [spec.fmt(d), t]    spec.incr(d)  return ret# @privateminutesSpecHelper = (interval) ->  span: interval * 60 * 1000  start: (d) -> new Date(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours())  fmt: (d) -> "#{Morris.pad2(d.getHours())}:#{Morris.pad2(d.getMinutes())}"  incr: (d) -> d.setUTCMinutes(d.getUTCMinutes() + interval)# @privatesecondsSpecHelper = (interval) ->  span: interval * 1000  start: (d) -> new Date(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes())  fmt: (d) -> "#{Morris.pad2(d.getHours())}:#{Morris.pad2(d.getMinutes())}:#{Morris.pad2(d.getSeconds())}"  incr: (d) -> d.setUTCSeconds(d.getUTCSeconds() + interval)Morris.LABEL_SPECS =  "decade":    span: 172800000000 # 10 * 365 * 24 * 60 * 60 * 1000    start: (d) -> new Date(d.getFullYear() - d.getFullYear() % 10, 0, 1)    fmt: (d) -> "#{d.getFullYear()}"    incr: (d) -> d.setFullYear(d.getFullYear() + 10)  "year":    span: 17280000000 # 365 * 24 * 60 * 60 * 1000    start: (d) -> new Date(d.getFullYear(), 0, 1)    fmt: (d) -> "#{d.getFullYear()}"    incr: (d) -> d.setFullYear(d.getFullYear() + 1)  "month":    span: 2419200000 # 28 * 24 * 60 * 60 * 1000    start: (d) -> new Date(d.getFullYear(), d.getMonth(), 1)    fmt: (d) -> "#{d.getFullYear()}-#{Morris.pad2(d.getMonth() + 1)}"    incr: (d) -> d.setMonth(d.getMonth() + 1)  "week":    span: 604800000 # 7 * 24 * 60 * 60 * 1000    start: (d) -> new Date(d.getFullYear(), d.getMonth(), d.getDate())    fmt: (d) -> "#{d.getFullYear()}-#{Morris.pad2(d.getMonth() + 1)}-#{Morris.pad2(d.getDate())}"    incr: (d) -> d.setDate(d.getDate() + 7)  "day":    span: 86400000 # 24 * 60 * 60 * 1000    start: (d) -> new Date(d.getFullYear(), d.getMonth(), d.getDate())    fmt: (d) -> "#{d.getFullYear()}-#{Morris.pad2(d.getMonth() + 1)}-#{Morris.pad2(d.getDate())}"    incr: (d) -> d.setDate(d.getDate() + 1)  "hour": minutesSpecHelper(60)  "30min": minutesSpecHelper(30)  "15min": minutesSpecHelper(15)  "10min": minutesSpecHelper(10)  "5min": minutesSpecHelper(5)  "minute": minutesSpecHelper(1)  "30sec": secondsSpecHelper(30)  "15sec": secondsSpecHelper(15)  "10sec": secondsSpecHelper(10)  "5sec": secondsSpecHelper(5)  "second": secondsSpecHelper(1)Morris.AUTO_LABEL_ORDER = [  "decade", "year", "month", "week", "day", "hour",  "30min", "15min", "10min", "5min", "minute",  "30sec", "15sec", "10sec", "5sec", "second"]
 |