aboutsummaryrefslogtreecommitdiffhomepage
path: root/public/bower_components/morris.js/lib/morris.line.coffee
diff options
context:
space:
mode:
Diffstat (limited to 'public/bower_components/morris.js/lib/morris.line.coffee')
-rw-r--r--public/bower_components/morris.js/lib/morris.line.coffee405
1 files changed, 405 insertions, 0 deletions
diff --git a/public/bower_components/morris.js/lib/morris.line.coffee b/public/bower_components/morris.js/lib/morris.line.coffee
new file mode 100644
index 0000000..ea5e7dd
--- /dev/null
+++ b/public/bower_components/morris.js/lib/morris.line.coffee
@@ -0,0 +1,405 @@
+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
+#
+# @private
+Morris.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
+
+# @private
+minutesSpecHelper = (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)
+
+# @private
+secondsSpecHelper = (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"
+]