mirror of
https://github.com/morrisjs/morris.js.git
synced 2024-11-10 21:36:34 +01:00
WIP Refactor grid drawing into a base class.
This commit is contained in:
parent
67fbc481aa
commit
d5b8071dbb
@ -25,16 +25,12 @@ var day_data = [
|
|||||||
{"period": "2012-09-15", "licensed": 3201, "sorned": 656},
|
{"period": "2012-09-15", "licensed": 3201, "sorned": 656},
|
||||||
{"period": "2012-09-10", "licensed": 3215, "sorned": 622}
|
{"period": "2012-09-10", "licensed": 3215, "sorned": 622}
|
||||||
];
|
];
|
||||||
Morris.Line({
|
line = Morris.Line({
|
||||||
element: 'graph',
|
element: 'graph',
|
||||||
data: day_data,
|
data: day_data,
|
||||||
xkey: 'period',
|
xkey: 'period',
|
||||||
ykeys: ['licensed', 'sorned'],
|
ykeys: ['licensed', 'sorned'],
|
||||||
labels: ['Licensed', 'SORN'],
|
labels: ['Licensed', 'SORN']
|
||||||
/* custom label formatting with `xLabelFormat` */
|
|
||||||
xLabelFormat: function(d) { return (d.getMonth()+1)+'/'+d.getDate()+'/'+d.getFullYear(); },
|
|
||||||
/* setting `xLabels` is recommended when using xLabelFormat */
|
|
||||||
xLabels: 'day'
|
|
||||||
});
|
});
|
||||||
</pre>
|
</pre>
|
||||||
</body>
|
</body>
|
||||||
|
292
lib/morris.grid.coffee
Normal file
292
lib/morris.grid.coffee
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
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 @el is null or @el.length == 0
|
||||||
|
throw new Error("Graph container element not found")
|
||||||
|
|
||||||
|
@options = $.extend {}, @gridDefaults, (@defaults || {}), options
|
||||||
|
|
||||||
|
# bail if there's no data
|
||||||
|
if @options.data is undefined or @options.data.length is 0
|
||||||
|
return
|
||||||
|
|
||||||
|
# backwards compatibility for units -> postUnits
|
||||||
|
if typeof @options.units is 'string'
|
||||||
|
@options.postUnits = options.units
|
||||||
|
|
||||||
|
# the raphael drawing instance
|
||||||
|
@r = new Raphael(@el[0])
|
||||||
|
|
||||||
|
# some redraw stuff
|
||||||
|
@elementWidth = null
|
||||||
|
@elementHeight = null
|
||||||
|
@dirty = false
|
||||||
|
|
||||||
|
# more stuff
|
||||||
|
@init() if @init
|
||||||
|
|
||||||
|
# load data
|
||||||
|
@setData(@options.data)
|
||||||
|
|
||||||
|
# Default options
|
||||||
|
#
|
||||||
|
gridDefaults:
|
||||||
|
dateFormat: null
|
||||||
|
gridLineColor: '#aaa'
|
||||||
|
gridStrokeWidth: 0.5
|
||||||
|
gridTextColor: '#888'
|
||||||
|
gridTextSize: 12
|
||||||
|
numLines: 5
|
||||||
|
padding: 25
|
||||||
|
parseTime: true
|
||||||
|
postUnits: ''
|
||||||
|
preUnits: ''
|
||||||
|
ymax: 'auto'
|
||||||
|
ymin: 'auto 0'
|
||||||
|
|
||||||
|
# Update the data series and redraw the chart.
|
||||||
|
#
|
||||||
|
setData: (data, redraw = true) ->
|
||||||
|
# shallow copy data
|
||||||
|
@options.data = data.slice()
|
||||||
|
if @parseTime
|
||||||
|
@options.data = @options.data.sort (a, b) =>
|
||||||
|
(a[@options.xkey] < b[@options.xkey]) - (b[@options.xkey] < a[@options.xkey])
|
||||||
|
|
||||||
|
# extract series data
|
||||||
|
@series = []
|
||||||
|
for ykey in @options.ykeys
|
||||||
|
seriesData = []
|
||||||
|
for d in @options.data
|
||||||
|
y = d[ykey]
|
||||||
|
seriesData.push switch typeof y
|
||||||
|
when 'number' then y
|
||||||
|
when 'string' then parseFloat y
|
||||||
|
else null
|
||||||
|
@series.push seriesData
|
||||||
|
|
||||||
|
# extract labels / x values
|
||||||
|
@columnLabels = $.map @options.data, (d) => d[@options.xkey]
|
||||||
|
if @options.parseTime
|
||||||
|
@xvals = $.map @columnLabels, (x) -> Morris.parseDate x
|
||||||
|
if @options.dateFormat
|
||||||
|
@columnLabels = $.map @xvals, (d) => @options.dateFormat d
|
||||||
|
else
|
||||||
|
@columnLabels = $.map @columnLabels, (d) =>
|
||||||
|
# default formatter for numeric timestamp labels
|
||||||
|
if typeof d is 'number' then new Date(d).toString() else d
|
||||||
|
else
|
||||||
|
@xvals = [0...@columnLabels.length]
|
||||||
|
|
||||||
|
# calculate horizontal range of the graph
|
||||||
|
@xmin = Math.min.apply null, @xvals
|
||||||
|
@xmax = Math.max.apply null, @xvals
|
||||||
|
if @xmin is @xmax
|
||||||
|
@xmin -= 1
|
||||||
|
@xmax += 1
|
||||||
|
|
||||||
|
# Compute the vertical range of the graph if desired
|
||||||
|
if typeof @options.ymax is 'string' and @options.ymax[0..3] is 'auto'
|
||||||
|
# use Array.concat to flatten arrays and find the max y value
|
||||||
|
ymax = Math.max.apply null, Array.prototype.concat.apply([], @series)
|
||||||
|
if @options.ymax.length > 5
|
||||||
|
@ymax = Math.max parseInt(@options.ymax[5..], 10), ymax
|
||||||
|
else
|
||||||
|
@ymax = ymax
|
||||||
|
else if typeof @options.ymax is 'string'
|
||||||
|
@ymax = parseInt(@options.ymax, 10)
|
||||||
|
else
|
||||||
|
@ymax = @options.ymax
|
||||||
|
if typeof @options.ymin is 'string' and @options.ymin[0..3] is 'auto'
|
||||||
|
ymin = Math.min.apply null, Array.prototype.concat.apply([], @series)
|
||||||
|
if @options.ymin.length > 5
|
||||||
|
@ymin = Math.min parseInt(@options.ymin[5..], 10), ymin
|
||||||
|
else
|
||||||
|
@ymin = ymin
|
||||||
|
else if typeof @options.ymin is 'string'
|
||||||
|
@ymin = parseInt(@options.ymin, 10)
|
||||||
|
else
|
||||||
|
@ymin = @options.ymin
|
||||||
|
if @ymin is @ymax
|
||||||
|
if @ymin is not 0 then @ymin -= 1
|
||||||
|
@ymax += 1
|
||||||
|
|
||||||
|
@yInterval = (@ymax - @ymin) / (@options.numLines - 1)
|
||||||
|
if @yInterval > 0 and @yInterval < 1
|
||||||
|
@precision = -Math.floor(Math.log(@yInterval) / Math.log(10))
|
||||||
|
else
|
||||||
|
@precision = 0
|
||||||
|
|
||||||
|
@dirty = true
|
||||||
|
@redraw() if redraw
|
||||||
|
|
||||||
|
_calc: ->
|
||||||
|
w = @el.width()
|
||||||
|
h = @el.height()
|
||||||
|
|
||||||
|
if @elementWidth != w or @elementHeight != h or @dirty
|
||||||
|
@elementWidth = w
|
||||||
|
@elementHeight = h
|
||||||
|
@dirty = false
|
||||||
|
# calculate grid dimensions
|
||||||
|
maxYLabelWidth = Math.max(
|
||||||
|
@measureText(@yAxisFormat(@ymin), @options.gridTextSize).width,
|
||||||
|
@measureText(@yAxisFormat(@ymax), @options.gridTextSize).width)
|
||||||
|
@left = maxYLabelWidth + @options.padding
|
||||||
|
@right = @elementWidth - @options.padding
|
||||||
|
@top = @options.padding
|
||||||
|
@bottom = @elementHeight - @options.padding - 1.5 * @options.gridTextSize
|
||||||
|
@width = @right - @left
|
||||||
|
@height = @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 @xvals.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: ->
|
||||||
|
@r.clear()
|
||||||
|
@_calc()
|
||||||
|
@drawGrid()
|
||||||
|
@draw() if @draw
|
||||||
|
|
||||||
|
# draw y axis labels, horizontal lines
|
||||||
|
#
|
||||||
|
drawGrid: ->
|
||||||
|
firstY = @ymin
|
||||||
|
lastY = @ymax
|
||||||
|
for lineY in [firstY..lastY] by @yInterval
|
||||||
|
v = parseFloat(lineY.toFixed(@precision))
|
||||||
|
y = @transY(v)
|
||||||
|
@r.text(@left - @options.padding / 2, y, @yAxisFormat(v))
|
||||||
|
.attr('font-size', @options.gridTextSize)
|
||||||
|
.attr('fill', @options.gridTextColor)
|
||||||
|
.attr('text-anchor', 'end')
|
||||||
|
@r.path("M#{@left},#{y}H#{@left + @width}")
|
||||||
|
.attr('stroke', @options.gridLineColor)
|
||||||
|
.attr('stroke-width', @options.gridStrokeWidth)
|
||||||
|
|
||||||
|
# @private
|
||||||
|
#
|
||||||
|
measureText: (text, fontSize = 12) ->
|
||||||
|
tt = @r.text(100, 100, text).attr('font-size', fontSize)
|
||||||
|
ret = tt.getBBox()
|
||||||
|
tt.remove()
|
||||||
|
ret
|
||||||
|
|
||||||
|
# @private
|
||||||
|
#
|
||||||
|
yAxisFormat: (label) -> @yLabelFormat(label)
|
||||||
|
|
||||||
|
# @private
|
||||||
|
#
|
||||||
|
yLabelFormat: (label) ->
|
||||||
|
"#{@options.preUnits}#{Morris.commas(label)}#{@options.postUnits}"
|
||||||
|
|
||||||
|
|
||||||
|
# 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()
|
||||||
|
|
@ -1,35 +1,14 @@
|
|||||||
class Morris.Line
|
class Morris.Line extends Morris.Grid
|
||||||
# Initialise the graph.
|
# Initialise the graph.
|
||||||
#
|
#
|
||||||
constructor: (options) ->
|
constructor: (options) ->
|
||||||
if not (this instanceof Morris.Line)
|
return new Morris.Line(options) unless (@ instanceof Morris.Line)
|
||||||
return new Morris.Line(options)
|
super(options)
|
||||||
if typeof options.element is 'string'
|
|
||||||
@el = $ document.getElementById(options.element)
|
|
||||||
else
|
|
||||||
@el = $ options.element
|
|
||||||
|
|
||||||
if @el == null || @el.length == 0
|
|
||||||
throw new Error("Graph placeholder not found.")
|
|
||||||
|
|
||||||
@options = $.extend {}, @defaults, options
|
|
||||||
# backwards compatibility for units -> postUnits
|
|
||||||
if typeof @options.units is 'string'
|
|
||||||
@options.postUnits = options.units
|
|
||||||
# bail if there's no data
|
|
||||||
if @options.data is undefined or @options.data.length is 0
|
|
||||||
return
|
|
||||||
@el.addClass 'graph-initialised'
|
|
||||||
|
|
||||||
# the raphael drawing instance
|
|
||||||
@r = new Raphael(@el[0])
|
|
||||||
|
|
||||||
|
init: ->
|
||||||
# Some instance variables for later
|
# Some instance variables for later
|
||||||
@pointGrow = Raphael.animation r: @options.pointSize + 3, 25, 'linear'
|
@pointGrow = Raphael.animation r: @options.pointSize + 3, 25, 'linear'
|
||||||
@pointShrink = Raphael.animation r: @options.pointSize, 25, 'linear'
|
@pointShrink = Raphael.animation r: @options.pointSize, 25, 'linear'
|
||||||
@elementWidth = null
|
|
||||||
@elementHeight = null
|
|
||||||
@dirty = false
|
|
||||||
# column hilight events
|
# column hilight events
|
||||||
@prevHilight = null
|
@prevHilight = null
|
||||||
@el.mousemove (evt) =>
|
@el.mousemove (evt) =>
|
||||||
@ -45,9 +24,6 @@ class Morris.Line
|
|||||||
@el.bind 'touchmove', touchHandler
|
@el.bind 'touchmove', touchHandler
|
||||||
@el.bind 'touchend', touchHandler
|
@el.bind 'touchend', touchHandler
|
||||||
|
|
||||||
@seriesLabels = @options.labels
|
|
||||||
@setData(@options.data)
|
|
||||||
|
|
||||||
# Default configuration
|
# Default configuration
|
||||||
#
|
#
|
||||||
defaults:
|
defaults:
|
||||||
@ -65,17 +41,6 @@ class Morris.Line
|
|||||||
pointWidths: [1]
|
pointWidths: [1]
|
||||||
pointStrokeColors: ['#ffffff']
|
pointStrokeColors: ['#ffffff']
|
||||||
pointFillColors: []
|
pointFillColors: []
|
||||||
ymax: 'auto'
|
|
||||||
ymin: 'auto 0'
|
|
||||||
marginTop: 25
|
|
||||||
marginRight: 25
|
|
||||||
marginBottom: 30
|
|
||||||
marginLeft: 25
|
|
||||||
numLines: 5
|
|
||||||
gridLineColor: '#aaa'
|
|
||||||
gridTextColor: '#888'
|
|
||||||
gridTextSize: 12
|
|
||||||
gridStrokeWidth: 0.5
|
|
||||||
hoverPaddingX: 10
|
hoverPaddingX: 10
|
||||||
hoverPaddingY: 5
|
hoverPaddingY: 5
|
||||||
hoverMargin: 10
|
hoverMargin: 10
|
||||||
@ -87,176 +52,41 @@ class Morris.Line
|
|||||||
hoverFontSize: 12
|
hoverFontSize: 12
|
||||||
smooth: true
|
smooth: true
|
||||||
hideHover: false
|
hideHover: false
|
||||||
parseTime: true
|
|
||||||
preUnits: ''
|
|
||||||
postUnits: ''
|
|
||||||
dateFormat: null
|
|
||||||
xLabels: 'auto'
|
xLabels: 'auto'
|
||||||
xLabelFormat: null
|
xLabelFormat: null
|
||||||
|
|
||||||
# Update the data series and redraw the chart.
|
|
||||||
#
|
|
||||||
setData: (data, redraw = true) ->
|
|
||||||
# shallow copy & sort data (if required)
|
|
||||||
@options.data = data.slice(0)
|
|
||||||
if @options.parseTime
|
|
||||||
@options.data.sort (a, b) =>
|
|
||||||
(a[@options.xkey] < b[@options.xkey]) - (b[@options.xkey] < a[@options.xkey])
|
|
||||||
else
|
|
||||||
@options.data.reverse()
|
|
||||||
|
|
||||||
# extract series data
|
|
||||||
@series = []
|
|
||||||
for ykey in @options.ykeys
|
|
||||||
series_data = []
|
|
||||||
for d in @options.data
|
|
||||||
series_data.push switch typeof d[ykey]
|
|
||||||
when 'number' then d[ykey]
|
|
||||||
when 'string' then parseFloat(d[ykey])
|
|
||||||
else null
|
|
||||||
@series.push(series_data)
|
|
||||||
|
|
||||||
# extract labels
|
|
||||||
@columnLabels = $.map @options.data, (d) => d[@options.xkey]
|
|
||||||
|
|
||||||
# translate x labels into nominal dates
|
|
||||||
if @options.parseTime
|
|
||||||
@xvals = $.map @columnLabels, (x) -> Morris.parseDate x
|
|
||||||
else
|
|
||||||
@xvals = [(@columnLabels.length-1)..0]
|
|
||||||
|
|
||||||
# format column labels
|
|
||||||
if @options.parseTime
|
|
||||||
if @options.dateFormat
|
|
||||||
@columnLabels = $.map @xvals, (d) => @options.dateFormat(d)
|
|
||||||
else
|
|
||||||
@columnLabels = $.map @columnLabels, (d) =>
|
|
||||||
# default formatter for numeric timestamp labels
|
|
||||||
if typeof d is 'number' then new Date(d).toString() else d
|
|
||||||
|
|
||||||
# calculate horizontal range of the graph
|
|
||||||
@xmin = Math.min.apply null, @xvals
|
|
||||||
@xmax = Math.max.apply null, @xvals
|
|
||||||
if @xmin is @xmax
|
|
||||||
@xmin -= 1
|
|
||||||
@xmax += 1
|
|
||||||
|
|
||||||
# Compute the vertical range of the graph if desired
|
|
||||||
if typeof @options.ymax is 'string' and @options.ymax[0..3] is 'auto'
|
|
||||||
# use Array.concat to flatten arrays and find the max y value
|
|
||||||
ymax = Math.max.apply null, Array.prototype.concat.apply([], @series)
|
|
||||||
if @options.ymax.length > 5
|
|
||||||
@ymax = Math.max parseInt(@options.ymax[5..], 10), ymax
|
|
||||||
else
|
|
||||||
@ymax = ymax
|
|
||||||
else if typeof @options.ymax is 'string'
|
|
||||||
@ymax = parseInt(@options.ymax, 10)
|
|
||||||
else
|
|
||||||
@ymax = @options.ymax
|
|
||||||
if typeof @options.ymin is 'string' and @options.ymin[0..3] is 'auto'
|
|
||||||
ymin = Math.min.apply null, Array.prototype.concat.apply([], @series)
|
|
||||||
if @options.ymin.length > 5
|
|
||||||
@ymin = Math.min parseInt(@options.ymin[5..], 10), ymin
|
|
||||||
else
|
|
||||||
@ymin = ymin
|
|
||||||
else if typeof @options.ymin is 'string'
|
|
||||||
@ymin = parseInt(@options.ymin, 10)
|
|
||||||
else
|
|
||||||
@ymin = @options.ymin
|
|
||||||
if @ymin is @ymax
|
|
||||||
if @ymin is not 0 then @ymin -= 1
|
|
||||||
@ymax += 1
|
|
||||||
|
|
||||||
@yInterval = (@ymax - @ymin) / (@options.numLines - 1)
|
|
||||||
if @yInterval > 0 and @yInterval < 1
|
|
||||||
@precision = -Math.floor(Math.log(@yInterval) / Math.log(10))
|
|
||||||
else
|
|
||||||
@precision = 0
|
|
||||||
|
|
||||||
@dirty = true
|
|
||||||
@redraw() if redraw
|
|
||||||
|
|
||||||
# Do any size-related calculations
|
# Do any size-related calculations
|
||||||
#
|
#
|
||||||
# @private
|
# @private
|
||||||
calc: ->
|
calc: ->
|
||||||
w = @el.width()
|
# calculate series data point coordinates
|
||||||
h = @el.height()
|
@columns = (@transX(x) for x in @xvals)
|
||||||
|
@seriesCoords = []
|
||||||
|
for s in @series
|
||||||
|
scoords = []
|
||||||
|
$.each s, (i, y) =>
|
||||||
|
if y == null
|
||||||
|
scoords.push(null)
|
||||||
|
else
|
||||||
|
scoords.push(x: @columns[i], y: @transY(y))
|
||||||
|
@seriesCoords.push(scoords)
|
||||||
|
# calculate hover margins
|
||||||
|
@hoverMargins = $.map @columns.slice(1), (x, i) => (x + @columns[i]) / 2
|
||||||
|
|
||||||
if @elementWidth != w or @elementHeight != h or @dirty
|
# Draws the line chart.
|
||||||
@elementWidth = w
|
|
||||||
@elementHeight = h
|
|
||||||
@dirty = false
|
|
||||||
# calculate grid dimensions
|
|
||||||
@maxYLabelWidth = Math.max(
|
|
||||||
@measureText(@yAxisFormat(@ymin), @options.gridTextSize).width,
|
|
||||||
@measureText(@yAxisFormat(@ymax), @options.gridTextSize).width)
|
|
||||||
@left = @maxYLabelWidth + @options.marginLeft
|
|
||||||
@width = @el.width() - @left - @options.marginRight
|
|
||||||
@height = @el.height() - @options.marginTop - @options.marginBottom
|
|
||||||
@dx = @width / (@xmax - @xmin)
|
|
||||||
@dy = @height / (@ymax - @ymin)
|
|
||||||
# calculate series data point coordinates
|
|
||||||
@columns = (@transX(x) for x in @xvals)
|
|
||||||
@seriesCoords = []
|
|
||||||
for s in @series
|
|
||||||
scoords = []
|
|
||||||
$.each s, (i, y) =>
|
|
||||||
if y == null
|
|
||||||
scoords.push(null)
|
|
||||||
else
|
|
||||||
scoords.push(x: @columns[i], y: @transY(y))
|
|
||||||
@seriesCoords.push(scoords)
|
|
||||||
# calculate hover margins
|
|
||||||
@hoverMargins = $.map @columns.slice(1), (x, i) => (x + @columns[i]) / 2
|
|
||||||
|
|
||||||
# quick translation helpers
|
|
||||||
#
|
#
|
||||||
# @private
|
draw: ->
|
||||||
transX: (x) =>
|
@drawXAxis()
|
||||||
if @xvals.length is 1
|
|
||||||
@left + @width / 2
|
|
||||||
else
|
|
||||||
@left + (x - @xmin) * @dx
|
|
||||||
|
|
||||||
# @private
|
|
||||||
transY: (y) =>
|
|
||||||
return @options.marginTop + @height - (y - @ymin) * @dy
|
|
||||||
|
|
||||||
# Clear and redraw the chart.
|
|
||||||
#
|
|
||||||
# If you need to re-size your charts, call this method after changing the
|
|
||||||
# size of the container element.
|
|
||||||
redraw: ->
|
|
||||||
@r.clear()
|
|
||||||
@calc()
|
|
||||||
@drawGrid()
|
|
||||||
@drawSeries()
|
@drawSeries()
|
||||||
@drawHover()
|
@drawHover()
|
||||||
@hilight(if @options.hideHover then null else 0)
|
@hilight(if @options.hideHover then null else 0)
|
||||||
|
|
||||||
# draw the grid, and axes labels
|
# draw the x-axis labels
|
||||||
#
|
#
|
||||||
# @private
|
# @private
|
||||||
drawGrid: ->
|
drawXAxis: ->
|
||||||
# draw y axis labels, horizontal lines
|
# draw x axis labels
|
||||||
firstY = @ymin
|
ypos = @bottom + @options.gridTextSize * 1.25
|
||||||
lastY = @ymax
|
|
||||||
|
|
||||||
|
|
||||||
for lineY in [firstY..lastY] by @yInterval
|
|
||||||
v = parseFloat(lineY.toFixed(@precision))
|
|
||||||
y = @transY(v)
|
|
||||||
@r.text(@left - @options.marginLeft/2, y, @yAxisFormat(v))
|
|
||||||
.attr('font-size', @options.gridTextSize)
|
|
||||||
.attr('fill', @options.gridTextColor)
|
|
||||||
.attr('text-anchor', 'end')
|
|
||||||
@r.path("M#{@left},#{y}H#{@left + @width}")
|
|
||||||
.attr('stroke', @options.gridLineColor)
|
|
||||||
.attr('stroke-width', @options.gridStrokeWidth)
|
|
||||||
|
|
||||||
## draw x axis labels
|
|
||||||
ypos = @options.marginTop + @height + @options.marginBottom / 2
|
|
||||||
xLabelMargin = 50 # make this an option?
|
xLabelMargin = 50 # make this an option?
|
||||||
prevLabelMargin = null
|
prevLabelMargin = null
|
||||||
drawLabel = (labelText, xpos) =>
|
drawLabel = (labelText, xpos) =>
|
||||||
@ -290,11 +120,11 @@ class Morris.Line
|
|||||||
# @private
|
# @private
|
||||||
drawSeries: ->
|
drawSeries: ->
|
||||||
for i in [@seriesCoords.length-1..0]
|
for i in [@seriesCoords.length-1..0]
|
||||||
coords = $.map(@seriesCoords[i], (c) -> c)
|
coords = @seriesCoords[i]
|
||||||
smooth = @options.smooth is true or
|
smooth = @options.smooth is true or
|
||||||
$.inArray(@options.ykeys[i], @options.smooth) > -1
|
$.inArray(@options.ykeys[i], @options.smooth) > -1
|
||||||
if coords.length > 1
|
if coords.length > 1
|
||||||
path = @createPath coords, @options.marginTop + @height, smooth
|
path = @createPath coords, @bottom, smooth
|
||||||
@r.path(path)
|
@r.path(path)
|
||||||
.attr('stroke', @colorForSeries(i))
|
.attr('stroke', @colorForSeries(i))
|
||||||
.attr('stroke-width', @options.lineWidth)
|
.attr('stroke-width', @options.lineWidth)
|
||||||
@ -313,7 +143,7 @@ class Morris.Line
|
|||||||
# create a path for a data series
|
# create a path for a data series
|
||||||
#
|
#
|
||||||
# @private
|
# @private
|
||||||
createPath: (coords, bottom, smooth) ->
|
createPath: (coords, smooth) ->
|
||||||
path = ""
|
path = ""
|
||||||
if smooth
|
if smooth
|
||||||
grads = @gradients coords
|
grads = @gradients coords
|
||||||
@ -327,9 +157,9 @@ class Morris.Line
|
|||||||
lg = grads[i - 1]
|
lg = grads[i - 1]
|
||||||
ix = (c.x - lc.x) / 4
|
ix = (c.x - lc.x) / 4
|
||||||
x1 = lc.x + ix
|
x1 = lc.x + ix
|
||||||
y1 = Math.min(bottom, lc.y + ix * lg)
|
y1 = Math.min(@bottom, lc.y + ix * lg)
|
||||||
x2 = c.x - ix
|
x2 = c.x - ix
|
||||||
y2 = Math.min(bottom, c.y - ix * g)
|
y2 = Math.min(@bottom, c.y - ix * g)
|
||||||
path += "C#{x1},#{y1},#{x2},#{y2},#{c.x},#{c.y}"
|
path += "C#{x1},#{y1},#{x2},#{y2},#{c.x},#{c.y}"
|
||||||
else
|
else
|
||||||
path = "M" + $.map(coords, (c) -> "#{c.x},#{c.y}").join("L")
|
path = "M" + $.map(coords, (c) -> "#{c.x},#{c.y}").join("L")
|
||||||
@ -378,7 +208,7 @@ class Morris.Line
|
|||||||
@hoverSet.show()
|
@hoverSet.show()
|
||||||
@xLabel.attr('text', @columnLabels[index])
|
@xLabel.attr('text', @columnLabels[index])
|
||||||
for i in [0..@series.length-1]
|
for i in [0..@series.length-1]
|
||||||
@yLabels[i].attr('text', "#{@seriesLabels[i]}: #{@yLabelFormat(@series[i][index])}")
|
@yLabels[i].attr('text', "#{@options.labels[i]}: #{@yLabelFormat(@series[i][index])}")
|
||||||
# recalculate hover box width
|
# recalculate hover box width
|
||||||
maxLabelWidth = Math.max.apply null, $.map @yLabels, (l) ->
|
maxLabelWidth = Math.max.apply null, $.map @yLabels, (l) ->
|
||||||
l.getBBox().width
|
l.getBBox().width
|
||||||
@ -388,13 +218,13 @@ class Morris.Line
|
|||||||
# move to y pos
|
# move to y pos
|
||||||
yloc = Math.min.apply null, $.map @series, (s) =>
|
yloc = Math.min.apply null, $.map @series, (s) =>
|
||||||
@transY s[index]
|
@transY s[index]
|
||||||
if yloc > @hoverHeight + @options.hoverPaddingY * 2 + @options.hoverMargin + @options.marginTop
|
if yloc > @hoverHeight + @options.hoverPaddingY * 2 + @options.hoverMargin + @top
|
||||||
yloc = yloc - @hoverHeight / 2 - @options.hoverPaddingY - @options.hoverMargin
|
yloc = yloc - @hoverHeight / 2 - @options.hoverPaddingY - @options.hoverMargin
|
||||||
else
|
else
|
||||||
yloc = yloc + @hoverHeight / 2 + @options.hoverPaddingY + @options.hoverMargin
|
yloc = yloc + @hoverHeight / 2 + @options.hoverPaddingY + @options.hoverMargin
|
||||||
yloc = Math.max @options.marginTop + @hoverHeight / 2 + @options.hoverPaddingY, yloc
|
yloc = Math.max @top + @hoverHeight / 2 + @options.hoverPaddingY, yloc
|
||||||
yloc = Math.min @options.marginTop + @height - @hoverHeight / 2 - @options.hoverPaddingY, yloc
|
yloc = Math.min @bottom - @hoverHeight / 2 - @options.hoverPaddingY, yloc
|
||||||
xloc = Math.min @left + @width - maxLabelWidth / 2 - @options.hoverPaddingX, @columns[index]
|
xloc = Math.min @right - maxLabelWidth / 2 - @options.hoverPaddingX, @columns[index]
|
||||||
xloc = Math.max @left + maxLabelWidth / 2 + @options.hoverPaddingX, xloc
|
xloc = Math.max @left + maxLabelWidth / 2 + @options.hoverPaddingX, xloc
|
||||||
@hoverSet.attr 'transform', "t#{xloc},#{yloc}"
|
@hoverSet.attr 'transform', "t#{xloc},#{yloc}"
|
||||||
|
|
||||||
@ -425,21 +255,6 @@ class Morris.Line
|
|||||||
@hilight hoverIndex
|
@hilight hoverIndex
|
||||||
break
|
break
|
||||||
|
|
||||||
# @private
|
|
||||||
measureText: (text, fontSize = 12) ->
|
|
||||||
tt = @r.text(100, 100, text).attr('font-size', fontSize)
|
|
||||||
ret = tt.getBBox()
|
|
||||||
tt.remove()
|
|
||||||
return ret
|
|
||||||
|
|
||||||
# @private
|
|
||||||
yAxisFormat: (label) ->
|
|
||||||
@yLabelFormat(label)
|
|
||||||
|
|
||||||
# @private
|
|
||||||
yLabelFormat: (label) ->
|
|
||||||
"#{@options.preUnits}#{Morris.commas(label)}#{@options.postUnits}"
|
|
||||||
|
|
||||||
# @private
|
# @private
|
||||||
colorForSeries: (index) ->
|
colorForSeries: (index) ->
|
||||||
@options.lineColors[index % @options.lineColors.length]
|
@options.lineColors[index % @options.lineColors.length]
|
||||||
@ -457,93 +272,6 @@ class Morris.Line
|
|||||||
@options.pointFillColors[index % @options.pointFillColors.length]
|
@options.pointFillColors[index % @options.pointFillColors.length]
|
||||||
|
|
||||||
|
|
||||||
# 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()
|
|
||||||
|
|
||||||
# generate a series of label, timestamp pairs for x-axis labels
|
# generate a series of label, timestamp pairs for x-axis labels
|
||||||
#
|
#
|
||||||
# @private
|
# @private
|
||||||
|
777
morris.js
777
morris.js
@ -283,143 +283,84 @@
|
|||||||
|
|
||||||
})(Morris.EventEmitter);
|
})(Morris.EventEmitter);
|
||||||
|
|
||||||
Morris.Line = (function() {
|
Morris.Grid = (function(_super) {
|
||||||
|
|
||||||
function Line(options) {
|
__extends(Grid, _super);
|
||||||
this.updateHilight = __bind(this.updateHilight, this);
|
|
||||||
|
|
||||||
this.hilight = __bind(this.hilight, this);
|
function Grid(options) {
|
||||||
|
|
||||||
this.updateHover = __bind(this.updateHover, this);
|
|
||||||
|
|
||||||
this.transY = __bind(this.transY, this);
|
|
||||||
|
|
||||||
this.transX = __bind(this.transX, this);
|
|
||||||
|
|
||||||
var touchHandler,
|
|
||||||
_this = this;
|
|
||||||
if (!(this instanceof Morris.Line)) {
|
|
||||||
return new Morris.Line(options);
|
|
||||||
}
|
|
||||||
if (typeof options.element === 'string') {
|
if (typeof options.element === 'string') {
|
||||||
this.el = $(document.getElementById(options.element));
|
this.el = $(document.getElementById(options.element));
|
||||||
} else {
|
} else {
|
||||||
this.el = $(options.element);
|
this.el = $(options.element);
|
||||||
}
|
}
|
||||||
if (this.el === null || this.el.length === 0) {
|
if (this.el === null || this.el.length === 0) {
|
||||||
throw new Error("Graph placeholder not found.");
|
throw new Error("Graph container element not found");
|
||||||
}
|
|
||||||
this.options = $.extend({}, this.defaults, options);
|
|
||||||
if (typeof this.options.units === 'string') {
|
|
||||||
this.options.postUnits = options.units;
|
|
||||||
}
|
}
|
||||||
|
this.options = $.extend({}, this.gridDefaults, this.defaults || {}, options);
|
||||||
if (this.options.data === void 0 || this.options.data.length === 0) {
|
if (this.options.data === void 0 || this.options.data.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.el.addClass('graph-initialised');
|
if (typeof this.options.units === 'string') {
|
||||||
|
this.options.postUnits = options.units;
|
||||||
|
}
|
||||||
this.r = new Raphael(this.el[0]);
|
this.r = new Raphael(this.el[0]);
|
||||||
this.pointGrow = Raphael.animation({
|
|
||||||
r: this.options.pointSize + 3
|
|
||||||
}, 25, 'linear');
|
|
||||||
this.pointShrink = Raphael.animation({
|
|
||||||
r: this.options.pointSize
|
|
||||||
}, 25, 'linear');
|
|
||||||
this.elementWidth = null;
|
this.elementWidth = null;
|
||||||
this.elementHeight = null;
|
this.elementHeight = null;
|
||||||
this.dirty = false;
|
this.dirty = false;
|
||||||
this.prevHilight = null;
|
if (this.init) {
|
||||||
this.el.mousemove(function(evt) {
|
this.init();
|
||||||
return _this.updateHilight(evt.pageX);
|
|
||||||
});
|
|
||||||
if (this.options.hideHover) {
|
|
||||||
this.el.mouseout(function(evt) {
|
|
||||||
return _this.hilight(null);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
touchHandler = function(evt) {
|
|
||||||
var touch;
|
|
||||||
touch = evt.originalEvent.touches[0] || evt.originalEvent.changedTouches[0];
|
|
||||||
_this.updateHilight(touch.pageX);
|
|
||||||
return touch;
|
|
||||||
};
|
|
||||||
this.el.bind('touchstart', touchHandler);
|
|
||||||
this.el.bind('touchmove', touchHandler);
|
|
||||||
this.el.bind('touchend', touchHandler);
|
|
||||||
this.seriesLabels = this.options.labels;
|
|
||||||
this.setData(this.options.data);
|
this.setData(this.options.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
Line.prototype.defaults = {
|
Grid.prototype.gridDefaults = {
|
||||||
lineWidth: 3,
|
dateFormat: null,
|
||||||
pointSize: 4,
|
|
||||||
lineColors: ['#0b62a4', '#7A92A3', '#4da74d', '#afd8f8', '#edc240', '#cb4b4b', '#9440ed'],
|
|
||||||
pointWidths: [1],
|
|
||||||
pointStrokeColors: ['#ffffff'],
|
|
||||||
pointFillColors: [],
|
|
||||||
ymax: 'auto',
|
|
||||||
ymin: 'auto 0',
|
|
||||||
marginTop: 25,
|
|
||||||
marginRight: 25,
|
|
||||||
marginBottom: 30,
|
|
||||||
marginLeft: 25,
|
|
||||||
numLines: 5,
|
|
||||||
gridLineColor: '#aaa',
|
gridLineColor: '#aaa',
|
||||||
|
gridStrokeWidth: 0.5,
|
||||||
gridTextColor: '#888',
|
gridTextColor: '#888',
|
||||||
gridTextSize: 12,
|
gridTextSize: 12,
|
||||||
gridStrokeWidth: 0.5,
|
numLines: 5,
|
||||||
hoverPaddingX: 10,
|
padding: 25,
|
||||||
hoverPaddingY: 5,
|
|
||||||
hoverMargin: 10,
|
|
||||||
hoverFillColor: '#fff',
|
|
||||||
hoverBorderColor: '#ccc',
|
|
||||||
hoverBorderWidth: 2,
|
|
||||||
hoverOpacity: 0.95,
|
|
||||||
hoverLabelColor: '#444',
|
|
||||||
hoverFontSize: 12,
|
|
||||||
smooth: true,
|
|
||||||
hideHover: false,
|
|
||||||
parseTime: true,
|
parseTime: true,
|
||||||
preUnits: '',
|
|
||||||
postUnits: '',
|
postUnits: '',
|
||||||
dateFormat: null,
|
preUnits: '',
|
||||||
xLabels: 'auto',
|
ymax: 'auto',
|
||||||
xLabelFormat: null
|
ymin: 'auto 0'
|
||||||
};
|
};
|
||||||
|
|
||||||
Line.prototype.setData = function(data, redraw) {
|
Grid.prototype.setData = function(data, redraw) {
|
||||||
var d, series_data, ykey, ymax, ymin, _i, _j, _k, _len, _len1, _ref, _ref1, _ref2, _results,
|
var d, seriesData, y, ykey, ymax, ymin, _i, _j, _k, _len, _len1, _ref, _ref1, _ref2, _results,
|
||||||
_this = this;
|
_this = this;
|
||||||
if (redraw == null) {
|
if (redraw == null) {
|
||||||
redraw = true;
|
redraw = true;
|
||||||
}
|
}
|
||||||
this.options.data = data.slice(0);
|
this.options.data = data.slice();
|
||||||
if (this.options.parseTime) {
|
if (this.parseTime) {
|
||||||
this.options.data.sort(function(a, b) {
|
this.options.data = this.options.data.sort(function(a, b) {
|
||||||
return (a[_this.options.xkey] < b[_this.options.xkey]) - (b[_this.options.xkey] < a[_this.options.xkey]);
|
return (a[_this.options.xkey] < b[_this.options.xkey]) - (b[_this.options.xkey] < a[_this.options.xkey]);
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
this.options.data.reverse();
|
|
||||||
}
|
}
|
||||||
this.series = [];
|
this.series = [];
|
||||||
_ref = this.options.ykeys;
|
_ref = this.options.ykeys;
|
||||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||||
ykey = _ref[_i];
|
ykey = _ref[_i];
|
||||||
series_data = [];
|
seriesData = [];
|
||||||
_ref1 = this.options.data;
|
_ref1 = this.options.data;
|
||||||
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
|
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
|
||||||
d = _ref1[_j];
|
d = _ref1[_j];
|
||||||
series_data.push((function() {
|
y = d[ykey];
|
||||||
switch (typeof d[ykey]) {
|
seriesData.push((function() {
|
||||||
|
switch (typeof y) {
|
||||||
case 'number':
|
case 'number':
|
||||||
return d[ykey];
|
return y;
|
||||||
case 'string':
|
case 'string':
|
||||||
return parseFloat(d[ykey]);
|
return parseFloat(y);
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
})());
|
})());
|
||||||
}
|
}
|
||||||
this.series.push(series_data);
|
this.series.push(seriesData);
|
||||||
}
|
}
|
||||||
this.columnLabels = $.map(this.options.data, function(d) {
|
this.columnLabels = $.map(this.options.data, function(d) {
|
||||||
return d[_this.options.xkey];
|
return d[_this.options.xkey];
|
||||||
@ -428,14 +369,6 @@
|
|||||||
this.xvals = $.map(this.columnLabels, function(x) {
|
this.xvals = $.map(this.columnLabels, function(x) {
|
||||||
return Morris.parseDate(x);
|
return Morris.parseDate(x);
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
this.xvals = (function() {
|
|
||||||
_results = [];
|
|
||||||
for (var _k = _ref2 = this.columnLabels.length - 1; _ref2 <= 0 ? _k <= 0 : _k >= 0; _ref2 <= 0 ? _k++ : _k--){ _results.push(_k); }
|
|
||||||
return _results;
|
|
||||||
}).apply(this);
|
|
||||||
}
|
|
||||||
if (this.options.parseTime) {
|
|
||||||
if (this.options.dateFormat) {
|
if (this.options.dateFormat) {
|
||||||
this.columnLabels = $.map(this.xvals, function(d) {
|
this.columnLabels = $.map(this.xvals, function(d) {
|
||||||
return _this.options.dateFormat(d);
|
return _this.options.dateFormat(d);
|
||||||
@ -449,6 +382,12 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this.xvals = (function() {
|
||||||
|
_results = [];
|
||||||
|
for (var _k = 0, _ref2 = this.columnLabels.length; 0 <= _ref2 ? _k < _ref2 : _k > _ref2; 0 <= _ref2 ? _k++ : _k--){ _results.push(_k); }
|
||||||
|
return _results;
|
||||||
|
}).apply(this);
|
||||||
}
|
}
|
||||||
this.xmin = Math.min.apply(null, this.xvals);
|
this.xmin = Math.min.apply(null, this.xvals);
|
||||||
this.xmax = Math.max.apply(null, this.xvals);
|
this.xmax = Math.max.apply(null, this.xvals);
|
||||||
@ -498,293 +437,65 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Line.prototype.calc = function() {
|
Grid.prototype._calc = function() {
|
||||||
var h, s, scoords, w, x, _i, _len, _ref,
|
var h, maxYLabelWidth, w;
|
||||||
_this = this;
|
|
||||||
w = this.el.width();
|
w = this.el.width();
|
||||||
h = this.el.height();
|
h = this.el.height();
|
||||||
if (this.elementWidth !== w || this.elementHeight !== h || this.dirty) {
|
if (this.elementWidth !== w || this.elementHeight !== h || this.dirty) {
|
||||||
this.elementWidth = w;
|
this.elementWidth = w;
|
||||||
this.elementHeight = h;
|
this.elementHeight = h;
|
||||||
this.dirty = false;
|
this.dirty = false;
|
||||||
this.maxYLabelWidth = Math.max(this.measureText(this.yAxisFormat(this.ymin), this.options.gridTextSize).width, this.measureText(this.yAxisFormat(this.ymax), this.options.gridTextSize).width);
|
maxYLabelWidth = Math.max(this.measureText(this.yAxisFormat(this.ymin), this.options.gridTextSize).width, this.measureText(this.yAxisFormat(this.ymax), this.options.gridTextSize).width);
|
||||||
this.left = this.maxYLabelWidth + this.options.marginLeft;
|
this.left = maxYLabelWidth + this.options.padding;
|
||||||
this.width = this.el.width() - this.left - this.options.marginRight;
|
this.right = this.elementWidth - this.options.padding;
|
||||||
this.height = this.el.height() - this.options.marginTop - this.options.marginBottom;
|
this.top = this.options.padding;
|
||||||
|
this.bottom = this.elementHeight - this.options.padding - 1.5 * this.options.gridTextSize;
|
||||||
|
this.width = this.right - this.left;
|
||||||
|
this.height = this.bottom - this.top;
|
||||||
this.dx = this.width / (this.xmax - this.xmin);
|
this.dx = this.width / (this.xmax - this.xmin);
|
||||||
this.dy = this.height / (this.ymax - this.ymin);
|
this.dy = this.height / (this.ymax - this.ymin);
|
||||||
this.columns = (function() {
|
if (this.calc) {
|
||||||
var _i, _len, _ref, _results;
|
return this.calc();
|
||||||
_ref = this.xvals;
|
|
||||||
_results = [];
|
|
||||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
|
||||||
x = _ref[_i];
|
|
||||||
_results.push(this.transX(x));
|
|
||||||
}
|
|
||||||
return _results;
|
|
||||||
}).call(this);
|
|
||||||
this.seriesCoords = [];
|
|
||||||
_ref = this.series;
|
|
||||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
|
||||||
s = _ref[_i];
|
|
||||||
scoords = [];
|
|
||||||
$.each(s, function(i, y) {
|
|
||||||
if (y === null) {
|
|
||||||
return scoords.push(null);
|
|
||||||
} else {
|
|
||||||
return scoords.push({
|
|
||||||
x: _this.columns[i],
|
|
||||||
y: _this.transY(y)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.seriesCoords.push(scoords);
|
|
||||||
}
|
}
|
||||||
return this.hoverMargins = $.map(this.columns.slice(1), function(x, i) {
|
|
||||||
return (x + _this.columns[i]) / 2;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Line.prototype.transX = function(x) {
|
Grid.prototype.transY = function(y) {
|
||||||
|
return this.bottom - (y - this.ymin) * this.dy;
|
||||||
|
};
|
||||||
|
|
||||||
|
Grid.prototype.transX = function(x) {
|
||||||
if (this.xvals.length === 1) {
|
if (this.xvals.length === 1) {
|
||||||
return this.left + this.width / 2;
|
return (this.left + this.right) / 2;
|
||||||
} else {
|
} else {
|
||||||
return this.left + (x - this.xmin) * this.dx;
|
return this.left + (x - this.xmin) * this.dx;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Line.prototype.transY = function(y) {
|
Grid.prototype.redraw = function() {
|
||||||
return this.options.marginTop + this.height - (y - this.ymin) * this.dy;
|
|
||||||
};
|
|
||||||
|
|
||||||
Line.prototype.redraw = function() {
|
|
||||||
this.r.clear();
|
this.r.clear();
|
||||||
this.calc();
|
this._calc();
|
||||||
this.drawGrid();
|
this.drawGrid();
|
||||||
this.drawSeries();
|
if (this.draw) {
|
||||||
this.drawHover();
|
return this.draw();
|
||||||
return this.hilight(this.options.hideHover ? null : 0);
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Line.prototype.drawGrid = function() {
|
Grid.prototype.drawGrid = function() {
|
||||||
var drawLabel, firstY, i, l, labelText, lastY, lineY, prevLabelMargin, v, xLabelMargin, y, ypos, _i, _j, _k, _len, _ref, _ref1, _ref2, _results, _results1,
|
var firstY, lastY, lineY, v, y, _i, _ref, _results;
|
||||||
_this = this;
|
|
||||||
firstY = this.ymin;
|
firstY = this.ymin;
|
||||||
lastY = this.ymax;
|
lastY = this.ymax;
|
||||||
|
_results = [];
|
||||||
for (lineY = _i = firstY, _ref = this.yInterval; firstY <= lastY ? _i <= lastY : _i >= lastY; lineY = _i += _ref) {
|
for (lineY = _i = firstY, _ref = this.yInterval; firstY <= lastY ? _i <= lastY : _i >= lastY; lineY = _i += _ref) {
|
||||||
v = parseFloat(lineY.toFixed(this.precision));
|
v = parseFloat(lineY.toFixed(this.precision));
|
||||||
y = this.transY(v);
|
y = this.transY(v);
|
||||||
this.r.text(this.left - this.options.marginLeft / 2, y, this.yAxisFormat(v)).attr('font-size', this.options.gridTextSize).attr('fill', this.options.gridTextColor).attr('text-anchor', 'end');
|
this.r.text(this.left - this.options.padding / 2, y, this.yAxisFormat(v)).attr('font-size', this.options.gridTextSize).attr('fill', this.options.gridTextColor).attr('text-anchor', 'end');
|
||||||
this.r.path("M" + this.left + "," + y + "H" + (this.left + this.width)).attr('stroke', this.options.gridLineColor).attr('stroke-width', this.options.gridStrokeWidth);
|
_results.push(this.r.path("M" + this.left + "," + y + "H" + (this.left + this.width)).attr('stroke', this.options.gridLineColor).attr('stroke-width', this.options.gridStrokeWidth));
|
||||||
}
|
|
||||||
ypos = this.options.marginTop + this.height + this.options.marginBottom / 2;
|
|
||||||
xLabelMargin = 50;
|
|
||||||
prevLabelMargin = null;
|
|
||||||
drawLabel = function(labelText, xpos) {
|
|
||||||
var label, labelBox;
|
|
||||||
label = _this.r.text(_this.transX(xpos), ypos, labelText).attr('font-size', _this.options.gridTextSize).attr('fill', _this.options.gridTextColor);
|
|
||||||
labelBox = label.getBBox();
|
|
||||||
if ((prevLabelMargin === null || prevLabelMargin <= labelBox.x) && labelBox.x >= 0 && (labelBox.x + labelBox.width) < _this.el.width()) {
|
|
||||||
return prevLabelMargin = labelBox.x + labelBox.width + xLabelMargin;
|
|
||||||
} else {
|
|
||||||
return label.remove();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (this.options.parseTime) {
|
|
||||||
if (this.columnLabels.length === 1 && this.options.xLabels === 'auto') {
|
|
||||||
return drawLabel(this.columnLabels[0], this.xvals[0]);
|
|
||||||
} else {
|
|
||||||
_ref1 = Morris.labelSeries(this.xmin, this.xmax, this.width, this.options.xLabels, this.options.xLabelFormat);
|
|
||||||
_results = [];
|
|
||||||
for (_j = 0, _len = _ref1.length; _j < _len; _j++) {
|
|
||||||
l = _ref1[_j];
|
|
||||||
_results.push(drawLabel(l[0], l[1]));
|
|
||||||
}
|
|
||||||
return _results;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_results1 = [];
|
|
||||||
for (i = _k = 0, _ref2 = this.columnLabels.length; 0 <= _ref2 ? _k <= _ref2 : _k >= _ref2; i = 0 <= _ref2 ? ++_k : --_k) {
|
|
||||||
labelText = this.columnLabels[this.columnLabels.length - i - 1];
|
|
||||||
_results1.push(drawLabel(labelText, i));
|
|
||||||
}
|
|
||||||
return _results1;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Line.prototype.drawSeries = function() {
|
|
||||||
var c, circle, coords, i, path, smooth, _i, _j, _ref, _ref1, _results;
|
|
||||||
for (i = _i = _ref = this.seriesCoords.length - 1; _ref <= 0 ? _i <= 0 : _i >= 0; i = _ref <= 0 ? ++_i : --_i) {
|
|
||||||
coords = $.map(this.seriesCoords[i], function(c) {
|
|
||||||
return c;
|
|
||||||
});
|
|
||||||
smooth = this.options.smooth === true || $.inArray(this.options.ykeys[i], this.options.smooth) > -1;
|
|
||||||
if (coords.length > 1) {
|
|
||||||
path = this.createPath(coords, this.options.marginTop + this.height, smooth);
|
|
||||||
this.r.path(path).attr('stroke', this.colorForSeries(i)).attr('stroke-width', this.options.lineWidth);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.seriesPoints = (function() {
|
|
||||||
var _j, _ref1, _results;
|
|
||||||
_results = [];
|
|
||||||
for (i = _j = 0, _ref1 = this.seriesCoords.length - 1; 0 <= _ref1 ? _j <= _ref1 : _j >= _ref1; i = 0 <= _ref1 ? ++_j : --_j) {
|
|
||||||
_results.push([]);
|
|
||||||
}
|
|
||||||
return _results;
|
|
||||||
}).call(this);
|
|
||||||
_results = [];
|
|
||||||
for (i = _j = _ref1 = this.seriesCoords.length - 1; _ref1 <= 0 ? _j <= 0 : _j >= 0; i = _ref1 <= 0 ? ++_j : --_j) {
|
|
||||||
_results.push((function() {
|
|
||||||
var _k, _len, _ref2, _results1;
|
|
||||||
_ref2 = this.seriesCoords[i];
|
|
||||||
_results1 = [];
|
|
||||||
for (_k = 0, _len = _ref2.length; _k < _len; _k++) {
|
|
||||||
c = _ref2[_k];
|
|
||||||
if (c === null) {
|
|
||||||
circle = null;
|
|
||||||
} else {
|
|
||||||
circle = this.r.circle(c.x, c.y, this.options.pointSize).attr('fill', this.pointFillColorForSeries(i) || this.colorForSeries(i)).attr('stroke-width', this.strokeWidthForSeries(i)).attr('stroke', this.strokeForSeries(i));
|
|
||||||
}
|
|
||||||
_results1.push(this.seriesPoints[i].push(circle));
|
|
||||||
}
|
|
||||||
return _results1;
|
|
||||||
}).call(this));
|
|
||||||
}
|
}
|
||||||
return _results;
|
return _results;
|
||||||
};
|
};
|
||||||
|
|
||||||
Line.prototype.createPath = function(coords, bottom, smooth) {
|
Grid.prototype.measureText = function(text, fontSize) {
|
||||||
var c, g, grads, i, ix, lc, lg, path, x1, x2, y1, y2, _i, _ref;
|
|
||||||
path = "";
|
|
||||||
if (smooth) {
|
|
||||||
grads = this.gradients(coords);
|
|
||||||
for (i = _i = 0, _ref = coords.length - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) {
|
|
||||||
c = coords[i];
|
|
||||||
if (i === 0) {
|
|
||||||
path += "M" + c.x + "," + c.y;
|
|
||||||
} else {
|
|
||||||
g = grads[i];
|
|
||||||
lc = coords[i - 1];
|
|
||||||
lg = grads[i - 1];
|
|
||||||
ix = (c.x - lc.x) / 4;
|
|
||||||
x1 = lc.x + ix;
|
|
||||||
y1 = Math.min(bottom, lc.y + ix * lg);
|
|
||||||
x2 = c.x - ix;
|
|
||||||
y2 = Math.min(bottom, c.y - ix * g);
|
|
||||||
path += "C" + x1 + "," + y1 + "," + x2 + "," + y2 + "," + c.x + "," + c.y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
path = "M" + $.map(coords, function(c) {
|
|
||||||
return "" + c.x + "," + c.y;
|
|
||||||
}).join("L");
|
|
||||||
}
|
|
||||||
return path;
|
|
||||||
};
|
|
||||||
|
|
||||||
Line.prototype.gradients = function(coords) {
|
|
||||||
return $.map(coords, function(c, i) {
|
|
||||||
if (i === 0) {
|
|
||||||
return (coords[1].y - c.y) / (coords[1].x - c.x);
|
|
||||||
} else if (i === (coords.length - 1)) {
|
|
||||||
return (c.y - coords[i - 1].y) / (c.x - coords[i - 1].x);
|
|
||||||
} else {
|
|
||||||
return (coords[i + 1].y - coords[i - 1].y) / (coords[i + 1].x - coords[i - 1].x);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Line.prototype.drawHover = function() {
|
|
||||||
var i, yLabel, _i, _ref, _results;
|
|
||||||
this.hoverHeight = this.options.hoverFontSize * 1.5 * (this.series.length + 1);
|
|
||||||
this.hover = this.r.rect(-10, -this.hoverHeight / 2 - this.options.hoverPaddingY, 20, this.hoverHeight + this.options.hoverPaddingY * 2, 10).attr('fill', this.options.hoverFillColor).attr('stroke', this.options.hoverBorderColor).attr('stroke-width', this.options.hoverBorderWidth).attr('opacity', this.options.hoverOpacity);
|
|
||||||
this.xLabel = this.r.text(0, (this.options.hoverFontSize * 0.75) - this.hoverHeight / 2, '').attr('fill', this.options.hoverLabelColor).attr('font-weight', 'bold').attr('font-size', this.options.hoverFontSize);
|
|
||||||
this.hoverSet = this.r.set();
|
|
||||||
this.hoverSet.push(this.hover);
|
|
||||||
this.hoverSet.push(this.xLabel);
|
|
||||||
this.yLabels = [];
|
|
||||||
_results = [];
|
|
||||||
for (i = _i = 0, _ref = this.series.length - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) {
|
|
||||||
yLabel = this.r.text(0, this.options.hoverFontSize * 1.5 * (i + 1.5) - this.hoverHeight / 2, '').attr('fill', this.colorForSeries(i)).attr('font-size', this.options.hoverFontSize);
|
|
||||||
this.yLabels.push(yLabel);
|
|
||||||
_results.push(this.hoverSet.push(yLabel));
|
|
||||||
}
|
|
||||||
return _results;
|
|
||||||
};
|
|
||||||
|
|
||||||
Line.prototype.updateHover = function(index) {
|
|
||||||
var i, maxLabelWidth, xloc, yloc, _i, _ref,
|
|
||||||
_this = this;
|
|
||||||
this.hoverSet.show();
|
|
||||||
this.xLabel.attr('text', this.columnLabels[index]);
|
|
||||||
for (i = _i = 0, _ref = this.series.length - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) {
|
|
||||||
this.yLabels[i].attr('text', "" + this.seriesLabels[i] + ": " + (this.yLabelFormat(this.series[i][index])));
|
|
||||||
}
|
|
||||||
maxLabelWidth = Math.max.apply(null, $.map(this.yLabels, function(l) {
|
|
||||||
return l.getBBox().width;
|
|
||||||
}));
|
|
||||||
maxLabelWidth = Math.max(maxLabelWidth, this.xLabel.getBBox().width);
|
|
||||||
this.hover.attr('width', maxLabelWidth + this.options.hoverPaddingX * 2);
|
|
||||||
this.hover.attr('x', -this.options.hoverPaddingX - maxLabelWidth / 2);
|
|
||||||
yloc = Math.min.apply(null, $.map(this.series, function(s) {
|
|
||||||
return _this.transY(s[index]);
|
|
||||||
}));
|
|
||||||
if (yloc > this.hoverHeight + this.options.hoverPaddingY * 2 + this.options.hoverMargin + this.options.marginTop) {
|
|
||||||
yloc = yloc - this.hoverHeight / 2 - this.options.hoverPaddingY - this.options.hoverMargin;
|
|
||||||
} else {
|
|
||||||
yloc = yloc + this.hoverHeight / 2 + this.options.hoverPaddingY + this.options.hoverMargin;
|
|
||||||
}
|
|
||||||
yloc = Math.max(this.options.marginTop + this.hoverHeight / 2 + this.options.hoverPaddingY, yloc);
|
|
||||||
yloc = Math.min(this.options.marginTop + this.height - this.hoverHeight / 2 - this.options.hoverPaddingY, yloc);
|
|
||||||
xloc = Math.min(this.left + this.width - maxLabelWidth / 2 - this.options.hoverPaddingX, this.columns[index]);
|
|
||||||
xloc = Math.max(this.left + maxLabelWidth / 2 + this.options.hoverPaddingX, xloc);
|
|
||||||
return this.hoverSet.attr('transform', "t" + xloc + "," + yloc);
|
|
||||||
};
|
|
||||||
|
|
||||||
Line.prototype.hideHover = function() {
|
|
||||||
return this.hoverSet.hide();
|
|
||||||
};
|
|
||||||
|
|
||||||
Line.prototype.hilight = function(index) {
|
|
||||||
var i, _i, _j, _ref, _ref1;
|
|
||||||
if (this.prevHilight !== null && this.prevHilight !== index) {
|
|
||||||
for (i = _i = 0, _ref = this.seriesPoints.length - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) {
|
|
||||||
if (this.seriesPoints[i][this.prevHilight]) {
|
|
||||||
this.seriesPoints[i][this.prevHilight].animate(this.pointShrink);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (index !== null && this.prevHilight !== index) {
|
|
||||||
for (i = _j = 0, _ref1 = this.seriesPoints.length - 1; 0 <= _ref1 ? _j <= _ref1 : _j >= _ref1; i = 0 <= _ref1 ? ++_j : --_j) {
|
|
||||||
if (this.seriesPoints[i][index]) {
|
|
||||||
this.seriesPoints[i][index].animate(this.pointGrow);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.updateHover(index);
|
|
||||||
}
|
|
||||||
this.prevHilight = index;
|
|
||||||
if (index === null) {
|
|
||||||
return this.hideHover();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Line.prototype.updateHilight = function(x) {
|
|
||||||
var hoverIndex, _i, _ref, _results;
|
|
||||||
x -= this.el.offset().left;
|
|
||||||
_results = [];
|
|
||||||
for (hoverIndex = _i = _ref = this.hoverMargins.length; _ref <= 0 ? _i <= 0 : _i >= 0; hoverIndex = _ref <= 0 ? ++_i : --_i) {
|
|
||||||
if (hoverIndex === 0 || this.hoverMargins[hoverIndex - 1] > x) {
|
|
||||||
this.hilight(hoverIndex);
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
_results.push(void 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return _results;
|
|
||||||
};
|
|
||||||
|
|
||||||
Line.prototype.measureText = function(text, fontSize) {
|
|
||||||
var ret, tt;
|
var ret, tt;
|
||||||
if (fontSize == null) {
|
if (fontSize == null) {
|
||||||
fontSize = 12;
|
fontSize = 12;
|
||||||
@ -795,33 +506,17 @@
|
|||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
Line.prototype.yAxisFormat = function(label) {
|
Grid.prototype.yAxisFormat = function(label) {
|
||||||
return this.yLabelFormat(label);
|
return this.yLabelFormat(label);
|
||||||
};
|
};
|
||||||
|
|
||||||
Line.prototype.yLabelFormat = function(label) {
|
Grid.prototype.yLabelFormat = function(label) {
|
||||||
return "" + this.options.preUnits + (Morris.commas(label)) + this.options.postUnits;
|
return "" + this.options.preUnits + (Morris.commas(label)) + this.options.postUnits;
|
||||||
};
|
};
|
||||||
|
|
||||||
Line.prototype.colorForSeries = function(index) {
|
return Grid;
|
||||||
return this.options.lineColors[index % this.options.lineColors.length];
|
|
||||||
};
|
|
||||||
|
|
||||||
Line.prototype.strokeWidthForSeries = function(index) {
|
})(Morris.EventEmitter);
|
||||||
return this.options.pointWidths[index % this.options.pointWidths.length];
|
|
||||||
};
|
|
||||||
|
|
||||||
Line.prototype.strokeForSeries = function(index) {
|
|
||||||
return this.options.pointStrokeColors[index % this.options.pointStrokeColors.length];
|
|
||||||
};
|
|
||||||
|
|
||||||
Line.prototype.pointFillColorForSeries = function(index) {
|
|
||||||
return this.options.pointFillColors[index % this.options.pointFillColors.length];
|
|
||||||
};
|
|
||||||
|
|
||||||
return Line;
|
|
||||||
|
|
||||||
})();
|
|
||||||
|
|
||||||
Morris.parseDate = function(date) {
|
Morris.parseDate = function(date) {
|
||||||
var isecs, m, msecs, n, o, offsetmins, p, q, r, ret, secs;
|
var isecs, m, msecs, n, o, offsetmins, p, q, r, ret, secs;
|
||||||
@ -880,6 +575,342 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Morris.Line = (function(_super) {
|
||||||
|
|
||||||
|
__extends(Line, _super);
|
||||||
|
|
||||||
|
function Line(options) {
|
||||||
|
this.updateHilight = __bind(this.updateHilight, this);
|
||||||
|
|
||||||
|
this.hilight = __bind(this.hilight, this);
|
||||||
|
|
||||||
|
this.updateHover = __bind(this.updateHover, this);
|
||||||
|
if (!(this instanceof Morris.Line)) {
|
||||||
|
return new Morris.Line(options);
|
||||||
|
}
|
||||||
|
Line.__super__.constructor.call(this, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
Line.prototype.init = function() {
|
||||||
|
var touchHandler,
|
||||||
|
_this = this;
|
||||||
|
this.pointGrow = Raphael.animation({
|
||||||
|
r: this.options.pointSize + 3
|
||||||
|
}, 25, 'linear');
|
||||||
|
this.pointShrink = Raphael.animation({
|
||||||
|
r: this.options.pointSize
|
||||||
|
}, 25, 'linear');
|
||||||
|
this.prevHilight = null;
|
||||||
|
this.el.mousemove(function(evt) {
|
||||||
|
return _this.updateHilight(evt.pageX);
|
||||||
|
});
|
||||||
|
if (this.options.hideHover) {
|
||||||
|
this.el.mouseout(function(evt) {
|
||||||
|
return _this.hilight(null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
touchHandler = function(evt) {
|
||||||
|
var touch;
|
||||||
|
touch = evt.originalEvent.touches[0] || evt.originalEvent.changedTouches[0];
|
||||||
|
_this.updateHilight(touch.pageX);
|
||||||
|
return touch;
|
||||||
|
};
|
||||||
|
this.el.bind('touchstart', touchHandler);
|
||||||
|
this.el.bind('touchmove', touchHandler);
|
||||||
|
return this.el.bind('touchend', touchHandler);
|
||||||
|
};
|
||||||
|
|
||||||
|
Line.prototype.defaults = {
|
||||||
|
lineWidth: 3,
|
||||||
|
pointSize: 4,
|
||||||
|
lineColors: ['#0b62a4', '#7A92A3', '#4da74d', '#afd8f8', '#edc240', '#cb4b4b', '#9440ed'],
|
||||||
|
pointWidths: [1],
|
||||||
|
pointStrokeColors: ['#ffffff'],
|
||||||
|
pointFillColors: [],
|
||||||
|
hoverPaddingX: 10,
|
||||||
|
hoverPaddingY: 5,
|
||||||
|
hoverMargin: 10,
|
||||||
|
hoverFillColor: '#fff',
|
||||||
|
hoverBorderColor: '#ccc',
|
||||||
|
hoverBorderWidth: 2,
|
||||||
|
hoverOpacity: 0.95,
|
||||||
|
hoverLabelColor: '#444',
|
||||||
|
hoverFontSize: 12,
|
||||||
|
smooth: true,
|
||||||
|
hideHover: false,
|
||||||
|
xLabels: 'auto',
|
||||||
|
xLabelFormat: null
|
||||||
|
};
|
||||||
|
|
||||||
|
Line.prototype.calc = function() {
|
||||||
|
var s, scoords, x, _i, _len, _ref,
|
||||||
|
_this = this;
|
||||||
|
this.columns = (function() {
|
||||||
|
var _i, _len, _ref, _results;
|
||||||
|
_ref = this.xvals;
|
||||||
|
_results = [];
|
||||||
|
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||||
|
x = _ref[_i];
|
||||||
|
_results.push(this.transX(x));
|
||||||
|
}
|
||||||
|
return _results;
|
||||||
|
}).call(this);
|
||||||
|
this.seriesCoords = [];
|
||||||
|
_ref = this.series;
|
||||||
|
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||||
|
s = _ref[_i];
|
||||||
|
scoords = [];
|
||||||
|
$.each(s, function(i, y) {
|
||||||
|
if (y === null) {
|
||||||
|
return scoords.push(null);
|
||||||
|
} else {
|
||||||
|
return scoords.push({
|
||||||
|
x: _this.columns[i],
|
||||||
|
y: _this.transY(y)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.seriesCoords.push(scoords);
|
||||||
|
}
|
||||||
|
return this.hoverMargins = $.map(this.columns.slice(1), function(x, i) {
|
||||||
|
return (x + _this.columns[i]) / 2;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Line.prototype.draw = function() {
|
||||||
|
this.drawXAxis();
|
||||||
|
this.drawSeries();
|
||||||
|
this.drawHover();
|
||||||
|
return this.hilight(this.options.hideHover ? null : 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
Line.prototype.drawXAxis = function() {
|
||||||
|
var drawLabel, i, l, labelText, prevLabelMargin, xLabelMargin, ypos, _i, _j, _len, _ref, _ref1, _results, _results1,
|
||||||
|
_this = this;
|
||||||
|
ypos = this.bottom + this.options.gridTextSize * 1.25;
|
||||||
|
xLabelMargin = 50;
|
||||||
|
prevLabelMargin = null;
|
||||||
|
drawLabel = function(labelText, xpos) {
|
||||||
|
var label, labelBox;
|
||||||
|
label = _this.r.text(_this.transX(xpos), ypos, labelText).attr('font-size', _this.options.gridTextSize).attr('fill', _this.options.gridTextColor);
|
||||||
|
labelBox = label.getBBox();
|
||||||
|
if ((prevLabelMargin === null || prevLabelMargin <= labelBox.x) && labelBox.x >= 0 && (labelBox.x + labelBox.width) < _this.el.width()) {
|
||||||
|
return prevLabelMargin = labelBox.x + labelBox.width + xLabelMargin;
|
||||||
|
} else {
|
||||||
|
return label.remove();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (this.options.parseTime) {
|
||||||
|
if (this.columnLabels.length === 1 && this.options.xLabels === 'auto') {
|
||||||
|
return drawLabel(this.columnLabels[0], this.xvals[0]);
|
||||||
|
} else {
|
||||||
|
_ref = Morris.labelSeries(this.xmin, this.xmax, this.width, this.options.xLabels, this.options.xLabelFormat);
|
||||||
|
_results = [];
|
||||||
|
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||||
|
l = _ref[_i];
|
||||||
|
_results.push(drawLabel(l[0], l[1]));
|
||||||
|
}
|
||||||
|
return _results;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_results1 = [];
|
||||||
|
for (i = _j = 0, _ref1 = this.columnLabels.length; 0 <= _ref1 ? _j <= _ref1 : _j >= _ref1; i = 0 <= _ref1 ? ++_j : --_j) {
|
||||||
|
labelText = this.columnLabels[this.columnLabels.length - i - 1];
|
||||||
|
_results1.push(drawLabel(labelText, i));
|
||||||
|
}
|
||||||
|
return _results1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Line.prototype.drawSeries = function() {
|
||||||
|
var c, circle, coords, i, path, smooth, _i, _j, _ref, _ref1, _results;
|
||||||
|
for (i = _i = _ref = this.seriesCoords.length - 1; _ref <= 0 ? _i <= 0 : _i >= 0; i = _ref <= 0 ? ++_i : --_i) {
|
||||||
|
coords = this.seriesCoords[i];
|
||||||
|
smooth = this.options.smooth === true || $.inArray(this.options.ykeys[i], this.options.smooth) > -1;
|
||||||
|
if (coords.length > 1) {
|
||||||
|
path = this.createPath(coords, this.bottom, smooth);
|
||||||
|
this.r.path(path).attr('stroke', this.colorForSeries(i)).attr('stroke-width', this.options.lineWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.seriesPoints = (function() {
|
||||||
|
var _j, _ref1, _results;
|
||||||
|
_results = [];
|
||||||
|
for (i = _j = 0, _ref1 = this.seriesCoords.length - 1; 0 <= _ref1 ? _j <= _ref1 : _j >= _ref1; i = 0 <= _ref1 ? ++_j : --_j) {
|
||||||
|
_results.push([]);
|
||||||
|
}
|
||||||
|
return _results;
|
||||||
|
}).call(this);
|
||||||
|
_results = [];
|
||||||
|
for (i = _j = _ref1 = this.seriesCoords.length - 1; _ref1 <= 0 ? _j <= 0 : _j >= 0; i = _ref1 <= 0 ? ++_j : --_j) {
|
||||||
|
_results.push((function() {
|
||||||
|
var _k, _len, _ref2, _results1;
|
||||||
|
_ref2 = this.seriesCoords[i];
|
||||||
|
_results1 = [];
|
||||||
|
for (_k = 0, _len = _ref2.length; _k < _len; _k++) {
|
||||||
|
c = _ref2[_k];
|
||||||
|
if (c === null) {
|
||||||
|
circle = null;
|
||||||
|
} else {
|
||||||
|
circle = this.r.circle(c.x, c.y, this.options.pointSize).attr('fill', this.pointFillColorForSeries(i) || this.colorForSeries(i)).attr('stroke-width', this.strokeWidthForSeries(i)).attr('stroke', this.strokeForSeries(i));
|
||||||
|
}
|
||||||
|
_results1.push(this.seriesPoints[i].push(circle));
|
||||||
|
}
|
||||||
|
return _results1;
|
||||||
|
}).call(this));
|
||||||
|
}
|
||||||
|
return _results;
|
||||||
|
};
|
||||||
|
|
||||||
|
Line.prototype.createPath = function(coords, smooth) {
|
||||||
|
var c, g, grads, i, ix, lc, lg, path, x1, x2, y1, y2, _i, _ref;
|
||||||
|
path = "";
|
||||||
|
if (smooth) {
|
||||||
|
grads = this.gradients(coords);
|
||||||
|
for (i = _i = 0, _ref = coords.length - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) {
|
||||||
|
c = coords[i];
|
||||||
|
if (i === 0) {
|
||||||
|
path += "M" + c.x + "," + c.y;
|
||||||
|
} else {
|
||||||
|
g = grads[i];
|
||||||
|
lc = coords[i - 1];
|
||||||
|
lg = grads[i - 1];
|
||||||
|
ix = (c.x - lc.x) / 4;
|
||||||
|
x1 = lc.x + ix;
|
||||||
|
y1 = Math.min(this.bottom, lc.y + ix * lg);
|
||||||
|
x2 = c.x - ix;
|
||||||
|
y2 = Math.min(this.bottom, c.y - ix * g);
|
||||||
|
path += "C" + x1 + "," + y1 + "," + x2 + "," + y2 + "," + c.x + "," + c.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
path = "M" + $.map(coords, function(c) {
|
||||||
|
return "" + c.x + "," + c.y;
|
||||||
|
}).join("L");
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
};
|
||||||
|
|
||||||
|
Line.prototype.gradients = function(coords) {
|
||||||
|
return $.map(coords, function(c, i) {
|
||||||
|
if (i === 0) {
|
||||||
|
return (coords[1].y - c.y) / (coords[1].x - c.x);
|
||||||
|
} else if (i === (coords.length - 1)) {
|
||||||
|
return (c.y - coords[i - 1].y) / (c.x - coords[i - 1].x);
|
||||||
|
} else {
|
||||||
|
return (coords[i + 1].y - coords[i - 1].y) / (coords[i + 1].x - coords[i - 1].x);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Line.prototype.drawHover = function() {
|
||||||
|
var i, yLabel, _i, _ref, _results;
|
||||||
|
this.hoverHeight = this.options.hoverFontSize * 1.5 * (this.series.length + 1);
|
||||||
|
this.hover = this.r.rect(-10, -this.hoverHeight / 2 - this.options.hoverPaddingY, 20, this.hoverHeight + this.options.hoverPaddingY * 2, 10).attr('fill', this.options.hoverFillColor).attr('stroke', this.options.hoverBorderColor).attr('stroke-width', this.options.hoverBorderWidth).attr('opacity', this.options.hoverOpacity);
|
||||||
|
this.xLabel = this.r.text(0, (this.options.hoverFontSize * 0.75) - this.hoverHeight / 2, '').attr('fill', this.options.hoverLabelColor).attr('font-weight', 'bold').attr('font-size', this.options.hoverFontSize);
|
||||||
|
this.hoverSet = this.r.set();
|
||||||
|
this.hoverSet.push(this.hover);
|
||||||
|
this.hoverSet.push(this.xLabel);
|
||||||
|
this.yLabels = [];
|
||||||
|
_results = [];
|
||||||
|
for (i = _i = 0, _ref = this.series.length - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) {
|
||||||
|
yLabel = this.r.text(0, this.options.hoverFontSize * 1.5 * (i + 1.5) - this.hoverHeight / 2, '').attr('fill', this.colorForSeries(i)).attr('font-size', this.options.hoverFontSize);
|
||||||
|
this.yLabels.push(yLabel);
|
||||||
|
_results.push(this.hoverSet.push(yLabel));
|
||||||
|
}
|
||||||
|
return _results;
|
||||||
|
};
|
||||||
|
|
||||||
|
Line.prototype.updateHover = function(index) {
|
||||||
|
var i, maxLabelWidth, xloc, yloc, _i, _ref,
|
||||||
|
_this = this;
|
||||||
|
this.hoverSet.show();
|
||||||
|
this.xLabel.attr('text', this.columnLabels[index]);
|
||||||
|
for (i = _i = 0, _ref = this.series.length - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) {
|
||||||
|
this.yLabels[i].attr('text', "" + this.options.labels[i] + ": " + (this.yLabelFormat(this.series[i][index])));
|
||||||
|
}
|
||||||
|
maxLabelWidth = Math.max.apply(null, $.map(this.yLabels, function(l) {
|
||||||
|
return l.getBBox().width;
|
||||||
|
}));
|
||||||
|
maxLabelWidth = Math.max(maxLabelWidth, this.xLabel.getBBox().width);
|
||||||
|
this.hover.attr('width', maxLabelWidth + this.options.hoverPaddingX * 2);
|
||||||
|
this.hover.attr('x', -this.options.hoverPaddingX - maxLabelWidth / 2);
|
||||||
|
yloc = Math.min.apply(null, $.map(this.series, function(s) {
|
||||||
|
return _this.transY(s[index]);
|
||||||
|
}));
|
||||||
|
if (yloc > this.hoverHeight + this.options.hoverPaddingY * 2 + this.options.hoverMargin + this.top) {
|
||||||
|
yloc = yloc - this.hoverHeight / 2 - this.options.hoverPaddingY - this.options.hoverMargin;
|
||||||
|
} else {
|
||||||
|
yloc = yloc + this.hoverHeight / 2 + this.options.hoverPaddingY + this.options.hoverMargin;
|
||||||
|
}
|
||||||
|
yloc = Math.max(this.top + this.hoverHeight / 2 + this.options.hoverPaddingY, yloc);
|
||||||
|
yloc = Math.min(this.bottom - this.hoverHeight / 2 - this.options.hoverPaddingY, yloc);
|
||||||
|
xloc = Math.min(this.right - maxLabelWidth / 2 - this.options.hoverPaddingX, this.columns[index]);
|
||||||
|
xloc = Math.max(this.left + maxLabelWidth / 2 + this.options.hoverPaddingX, xloc);
|
||||||
|
return this.hoverSet.attr('transform', "t" + xloc + "," + yloc);
|
||||||
|
};
|
||||||
|
|
||||||
|
Line.prototype.hideHover = function() {
|
||||||
|
return this.hoverSet.hide();
|
||||||
|
};
|
||||||
|
|
||||||
|
Line.prototype.hilight = function(index) {
|
||||||
|
var i, _i, _j, _ref, _ref1;
|
||||||
|
if (this.prevHilight !== null && this.prevHilight !== index) {
|
||||||
|
for (i = _i = 0, _ref = this.seriesPoints.length - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) {
|
||||||
|
if (this.seriesPoints[i][this.prevHilight]) {
|
||||||
|
this.seriesPoints[i][this.prevHilight].animate(this.pointShrink);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (index !== null && this.prevHilight !== index) {
|
||||||
|
for (i = _j = 0, _ref1 = this.seriesPoints.length - 1; 0 <= _ref1 ? _j <= _ref1 : _j >= _ref1; i = 0 <= _ref1 ? ++_j : --_j) {
|
||||||
|
if (this.seriesPoints[i][index]) {
|
||||||
|
this.seriesPoints[i][index].animate(this.pointGrow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.updateHover(index);
|
||||||
|
}
|
||||||
|
this.prevHilight = index;
|
||||||
|
if (index === null) {
|
||||||
|
return this.hideHover();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Line.prototype.updateHilight = function(x) {
|
||||||
|
var hoverIndex, _i, _ref, _results;
|
||||||
|
x -= this.el.offset().left;
|
||||||
|
_results = [];
|
||||||
|
for (hoverIndex = _i = _ref = this.hoverMargins.length; _ref <= 0 ? _i <= 0 : _i >= 0; hoverIndex = _ref <= 0 ? ++_i : --_i) {
|
||||||
|
if (hoverIndex === 0 || this.hoverMargins[hoverIndex - 1] > x) {
|
||||||
|
this.hilight(hoverIndex);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
_results.push(void 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _results;
|
||||||
|
};
|
||||||
|
|
||||||
|
Line.prototype.colorForSeries = function(index) {
|
||||||
|
return this.options.lineColors[index % this.options.lineColors.length];
|
||||||
|
};
|
||||||
|
|
||||||
|
Line.prototype.strokeWidthForSeries = function(index) {
|
||||||
|
return this.options.pointWidths[index % this.options.pointWidths.length];
|
||||||
|
};
|
||||||
|
|
||||||
|
Line.prototype.strokeForSeries = function(index) {
|
||||||
|
return this.options.pointStrokeColors[index % this.options.pointStrokeColors.length];
|
||||||
|
};
|
||||||
|
|
||||||
|
Line.prototype.pointFillColorForSeries = function(index) {
|
||||||
|
return this.options.pointFillColors[index % this.options.pointFillColors.length];
|
||||||
|
};
|
||||||
|
|
||||||
|
return Line;
|
||||||
|
|
||||||
|
})(Morris.Grid);
|
||||||
|
|
||||||
Morris.labelSeries = function(dmin, dmax, pxwidth, specName, xLabelFormat) {
|
Morris.labelSeries = function(dmin, dmax, pxwidth, specName, xLabelFormat) {
|
||||||
var d, d0, ddensity, name, ret, s, spec, t, _i, _len, _ref;
|
var d, d0, ddensity, name, ret, s, spec, t, _i, _len, _ref;
|
||||||
ddensity = 200 * (dmax - dmin) / pxwidth;
|
ddensity = 200 * (dmax - dmin) / pxwidth;
|
||||||
|
2
morris.min.js
vendored
2
morris.min.js
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user