first version of hover refactored to be 100% html

This commit is contained in:
Marcin Chwedziak 2012-11-18 20:02:46 +01:00
parent 25698d6664
commit 9bdabf1d0d
31 changed files with 751 additions and 672 deletions

View File

@ -7,6 +7,7 @@
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="lib/prettify.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Area charts</h1>

View File

@ -7,6 +7,7 @@
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="lib/prettify.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Bar charts</h1>

View File

@ -7,6 +7,7 @@
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="lib/prettify.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Bar charts</h1>

View File

@ -7,6 +7,7 @@
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="lib/prettify.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Formatting Dates YYYY-MM-DD</h1>

View File

@ -7,6 +7,7 @@
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="lib/prettify.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Decimal Data</h1>

View File

@ -7,6 +7,7 @@
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="lib/prettify.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Donut Chart</h1>

View File

@ -7,6 +7,7 @@
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="lib/prettify.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Donut Chart</h1>

View File

@ -7,6 +7,7 @@
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="lib/prettify.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Time Events</h1>

View File

@ -7,6 +7,7 @@
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="lib/prettify.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Value Goals</h1>

View File

@ -7,6 +7,7 @@
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="lib/prettify.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Formatting Dates with YYYY-MM</h1>

View File

@ -7,6 +7,7 @@
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="lib/prettify.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Negative values</h1>

View File

@ -7,6 +7,7 @@
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="lib/prettify.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Non-continuous data</h1>

View File

@ -7,6 +7,7 @@
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="lib/prettify.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Formatting Non-date Arbitrary X-axis</h1>

View File

@ -7,6 +7,7 @@
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="lib/prettify.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Formatting Dates with Quarters</h1>

View File

@ -7,6 +7,7 @@
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="lib/prettify.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Timestamps</h1>

View File

@ -7,6 +7,7 @@
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="lib/prettify.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Updating data</h1>

View File

@ -7,6 +7,7 @@
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="lib/prettify.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Formatting Dates With Weeks</h1>

View File

@ -7,6 +7,7 @@
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="lib/prettify.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Formatting Dates YYYY</h1>

View File

@ -16,6 +16,7 @@ module.exports = function (grunt) {
'build/morris.coffee': [
'lib/morris.coffee',
'lib/morris.grid.coffee',
'lib/morris.hover.coffee',
'lib/morris.line.coffee',
'lib/morris.area.coffee',
'lib/morris.bar.coffee',
@ -23,6 +24,15 @@ module.exports = function (grunt) {
],
'build/spec.coffee': ['spec/support/**/*.coffee', 'spec/lib/**/*.coffee']
},
less: {
all: {
src: 'less/*.less',
dest: 'morris.css',
options: {
compress: true
}
}
},
min: {
'morris.min.js': 'morris.js'
},
@ -33,13 +43,14 @@ module.exports = function (grunt) {
}
},
watch: {
files: ['lib/**/*.coffee', 'spec/lib/**/*.coffee', 'spec/support/**/*.coffee'],
files: ['lib/**/*.coffee', 'spec/lib/**/*.coffee', 'spec/support/**/*.coffee', 'less/**/*.less'],
tasks: 'default'
}
});
grunt.loadNpmTasks('grunt-coffee');
grunt.loadNpmTasks('grunt-mocha');
grunt.loadNpmTasks('grunt-less');
grunt.registerTask('default', 'concat coffee min mocha');
grunt.registerTask('default', 'concat coffee less min mocha');
};

19
less/morris.core.less Normal file
View File

@ -0,0 +1,19 @@
div.morris-popup {
border-radius: 10px;
position: absolute;
z-index: 1000;
padding: 6px;
font: normal 13px/16px Arial, sans-serif;
color: #666;
background: rgba(255,255,255,.8);
border: solid 2px rgba(230,230,230,.8);
h4, p {
font: normal 13px/16px Arial, sans-serif;
text-align: center;
color: #666;
margin: 0;
}
h4 { font-weight: bold; }
}

View File

@ -31,7 +31,7 @@ class Morris.Area extends Morris.Line
super()
fillForSeries: (i) ->
color = Raphael.rgb2hsl @colorForSeries(i)
color = Raphael.rgb2hsl @colorFor(@data[i], i, 'line')
Raphael.hsl(
color.h,
Math.min(255, color.s * 0.75),

View File

@ -1,29 +1,22 @@
class Morris.Bar extends Morris.Grid
# Initialise the graph.
#
@include Morris.Hover
# override hoverCalculatePosition
hoverGetPosition: (index) ->
[x, y] = Morris.Hover.hoverGetPosition.call(this, index)
[x, (@top + @bottom)/2 - @hoverHeight/2]
constructor: (options) ->
return new Morris.Bar(options) unless (@ instanceof Morris.Bar)
super($.extend {}, options, parseTime: false)
# setup event handlers
#
init: ->
@prevHilight = null
@el.mousemove (evt) =>
@updateHilight evt.pageX
if @options.hideHover
@el.mouseout (evt) =>
@hilight null
touchHandler = (evt) =>
touch = evt.originalEvent.touches[0] or evt.originalEvent.changedTouches[0]
@updateHilight touch.pageX
return touch
@el.bind 'touchstart', touchHandler
@el.bind 'touchmove', touchHandler
@el.bind 'touchend', touchHandler
# Default configuration
#
@hoverConfigure @options.hoverOptions
postInit: ->
@hoverInit()
defaults:
barSizeRatio: 0.75
barGap: 3
@ -36,23 +29,13 @@ class Morris.Bar extends Morris.Grid
'#cb4b4b'
'#9440ed'
]
hoverPaddingX: 10
hoverPaddingY: 5
hoverMargin: 10
hoverFillColor: '#fff'
hoverBorderColor: '#ccc'
hoverBorderWidth: 2
hoverOpacity: 0.95
hoverLabelColor: '#444'
hoverFontSize: 12
hideHover: false
# Do any size-related calculations
#
# @private
calc: ->
@calcBars()
@calcHoverMargins()
@hoverCalculateMargins()
# calculate series data bars coordinates and sizes
#
@ -63,21 +46,12 @@ class Morris.Bar extends Morris.Grid
row._y = for y in row.y
if y? then @transY(y) else null
# calculate hover margins
#
# @private
calcHoverMargins: ->
@hoverMargins = for i in [1...@data.length]
@left + i * @width / @data.length
# Draws the bar chart.
#
draw: ->
@drawXAxis()
@drawSeries()
@drawHover()
@hilight(if @options.hideHover then null else @data.length - 1)
# draw the x-axis labels
#
# @private
@ -125,69 +99,6 @@ class Morris.Bar extends Morris.Grid
else
null
# draw the hover tooltip
#
# @private
drawHover: ->
# hover labels
@hoverHeight = @options.hoverFontSize * 1.5 * (@options.ykeys.length + 1)
@hover = @r.rect(-10, -@hoverHeight / 2 - @options.hoverPaddingY, 20, @hoverHeight + @options.hoverPaddingY * 2, 10)
.attr('fill', @options.hoverFillColor)
.attr('stroke', @options.hoverBorderColor)
.attr('stroke-width', @options.hoverBorderWidth)
.attr('opacity', @options.hoverOpacity)
@xLabel = @r.text(0, (@options.hoverFontSize * 0.75) - @hoverHeight / 2, '')
.attr('fill', @options.hoverLabelColor)
.attr('font-weight', 'bold')
.attr('font-size', @options.hoverFontSize)
@hoverSet = @r.set()
@hoverSet.push(@hover)
@hoverSet.push(@xLabel)
@yLabels = []
for i in [0...@options.ykeys.length]
yLabel = @r.text(0, @options.hoverFontSize * 1.5 * (i + 1.5) - @hoverHeight / 2, '')
.attr('font-size', @options.hoverFontSize)
@yLabels.push(yLabel)
@hoverSet.push(yLabel)
# @private
updateHover: (index) =>
@hoverSet.show()
row = @data[index]
@xLabel.attr('text', row.label)
for y, i in row.y
@yLabels[i].attr('fill', @colorFor(row, i, 'hover'))
@yLabels[i].attr('text', "#{@options.labels[i]}: #{@yLabelFormat(y)}")
# recalculate hover box width
maxLabelWidth = Math.max.apply null, (l.getBBox().width for l in @yLabels)
maxLabelWidth = Math.max maxLabelWidth, @xLabel.getBBox().width
@hover.attr 'width', maxLabelWidth + @options.hoverPaddingX * 2
@hover.attr 'x', -@options.hoverPaddingX - maxLabelWidth / 2
# move to y pos
yloc = (@bottom + @top) / 2
xloc = Math.min @right - maxLabelWidth / 2 - @options.hoverPaddingX, @data[index]._x
xloc = Math.max @left + maxLabelWidth / 2 + @options.hoverPaddingX, xloc
@hoverSet.attr 'transform', "t#{xloc},#{yloc}"
# @private
hideHover: ->
@hoverSet.hide()
# @private
hilight: (index) =>
if index isnt null and @prevHilight isnt index
@updateHover index
@prevHilight = index
if not index?
@hideHover()
# @private
updateHilight: (x) =>
x -= @el.offset().left
for hoverIndex in [0...@hoverMargins.length]
break if @hoverMargins[hoverIndex] > x
@hilight hoverIndex
# @private
#
# @param row [Object] row data

View File

@ -2,10 +2,27 @@ Morris = window.Morris = {}
$ = jQuery
# Very simple multiple-inheritance implementation.
#
# @private
class Morris.Module
@extend: (obj) ->
for key, value of obj when key not in ['extended', 'included']
@[key] = value
obj.extended?.apply(@)
this
@include: (obj) ->
for key, value of obj when key not in ['extended', 'included']
@::[key] = value
obj.included?.apply(@)
# Very simple event-emitter class.
#
# @private
class Morris.EventEmitter
class Morris.EventEmitter extends Morris.Module
on: (name, handler) ->
unless @handlers?
@handlers = {}
@ -40,3 +57,85 @@ Morris.commas = (num) ->
# @example
# Morris.pad2(1) -> '01'
Morris.pad2 = (number) -> (if number < 10 then '0' else '') + number
# 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.setMinutes(d.getMinutes() + 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.setSeconds(d.getSeconds() + 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)
"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", "day", "hour",
"30min", "15min", "10min", "5min", "minute",
"30sec", "15sec", "10sec", "5sec", "second"
]

View File

@ -35,6 +35,8 @@ class Morris.Grid extends Morris.EventEmitter
# load data
@setData @options.data
@postInit() if @postInit
# Default options
#

113
lib/morris.hover.coffee Normal file
View File

@ -0,0 +1,113 @@
Morris.Hover =
hoverConfigure: (options) ->
@hoverOptions = $.extend {}, @hoverDefaults, options ? {}
hoverInit: ->
if @hoverOptions.enableHover
@hover = @hoverBuild()
@hoverBindEvents()
@hoverShow(if @hoverOptions.hideHover then null else @data.length - 1)
hoverDefaults:
enableHover: true
popupClass: "morris-popup"
hideHover: false
allowOverflow: false
pointMargin: 10
hoverFill: (index, row) -> @hoverFill(index, row)
hoverBindEvents: ->
@el.mousemove (evt) =>
@hoverUpdate evt.pageX
if @hoverOptions.hideHover
@el.mouseout (evt) =>
@hoverShow null
touchHandler = (evt) =>
touch = evt.originalEvent.touches[0] or evt.originalEvent.changedTouches[0]
@hoverUpdate touch.pageX
return touch
@el.bind 'touchstart', touchHandler
@el.bind 'touchmove', touchHandler
@el.bind 'touchend', touchHandler
@hover.mousemove (evt) -> evt.stopPropagation()
@hover.mouseout (evt) -> evt.stopPropagation()
@hover.bind 'touchstart', (evt) -> evt.stopPropagation()
@hover.bind 'touchmove', (evt) -> evt.stopPropagation()
@hover.bind 'touchend', (evt) -> evt.stopPropagation()
hoverCalculateMargins: ->
@hoverMargins = for i in [1...@data.length]
@left + i * @width / @data.length
hoverBuild: ->
hover = $ "<div/>"
hover.addClass "#{@hoverOptions.popupClass} js-morris-popup"
hover.appendTo @el
hover.hide()
hover
hoverUpdate: (x) ->
x -= @el.offset().left
for hoverIndex in [0...@hoverMargins.length]
break if @hoverMargins[hoverIndex] > x
@hoverShow hoverIndex
hoverShow: (index) ->
if index isnt null
@hover.html("")
@hoverOptions.hoverFill.call(@, index, @data[index])
@hoverPosition(index)
@fire "hover.show", index
@hover.show()
if not index?
@hoverHide()
hoverHide: ->
@hover.hide()
colorFor: (row, i, type) -> "inherit"
yLabelFormat: (label) -> Morris.commas(label)
hoverPosition: (index) ->
[x, y] = @hoverGetPosition index
@hover.css
top: "#{@el.offset().top + y}px"
left: "#{@el.offset().left + x}px"
hoverGetPosition: (index) ->
row = @data[index]
@hoverWidth = @hover.outerWidth(true)
@hoverHeight = @hover.outerHeight(true)
miny = y = Math.min.apply(null, (y for y in row._y when y isnt null).concat(@bottom))
x = row._x - @hoverWidth/2
y = miny
y = y - @hoverHeight - @hoverOptions.pointMargin
unless @hoverOptions.allowOverflow
if x < @left
x = row._x + @hoverOptions.pointMargin
else if x > @right - @hoverWidth
x = row._x - @hoverWidth - @hoverOptions.pointMargin
y = Math.max y, @top
y = Math.min y, (@bottom - @hoverHeight - @hoverOptions.pointMargin)
if y - miny < @hoverWidth + @hoverOptions.pointMargin
y = miny + @hoverOptions.pointMargin
[x, y]
hoverFill: (index, row) ->
xLabel = $ "<h4/>"
xLabel.text row.label
xLabel.appendTo @hover
for y, i in row.y
yLabel = $ "<p/>"
yLabel.css "color", @colorFor(row, i, "hover")
yLabel.text "#{@options.labels[i]}: #{@yLabelFormat(y)}"
yLabel.appendTo @hover

View File

@ -1,4 +1,6 @@
class Morris.Line extends Morris.Grid
@include Morris.Hover
# Initialise the graph.
#
constructor: (options) ->
@ -9,20 +11,27 @@ class Morris.Line extends Morris.Grid
# Some instance variables for later
@pointGrow = Raphael.animation r: @options.pointSize + 3, 25, 'linear'
@pointShrink = Raphael.animation r: @options.pointSize, 25, 'linear'
@hoverConfigure @options.hoverOptions
# column hilight events
@prevHilight = null
@el.mousemove (evt) =>
@updateHilight evt.pageX
if @options.hideHover
@el.mouseout (evt) =>
@hilight null
touchHandler = (evt) =>
touch = evt.originalEvent.touches[0] or evt.originalEvent.changedTouches[0]
@updateHilight touch.pageX
return touch
@el.bind 'touchstart', touchHandler
@el.bind 'touchmove', touchHandler
@el.bind 'touchend', touchHandler
if @options.hilight
@prevHilight = null
@el.mousemove (evt) =>
@updateHilight evt.pageX
if @options.hilightAutoHide
@el.mouseout (evt) =>
@hilight null
touchHandler = (evt) =>
touch = evt.originalEvent.touches[0] or evt.originalEvent.changedTouches[0]
@updateHilight touch.pageX
return touch
@el.bind 'touchstart', touchHandler
@el.bind 'touchmove', touchHandler
@el.bind 'touchend', touchHandler
postInit: ->
@hoverInit()
# Default configuration
#
@ -41,17 +50,9 @@ class Morris.Line extends Morris.Grid
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
hilight: true
hilightAutoHide: false
xLabels: 'auto'
xLabelFormat: null
@ -60,8 +61,9 @@ class Morris.Line extends Morris.Grid
# @private
calc: ->
@calcPoints()
@hoverCalculateMargins()
@generatePaths()
@calcHoverMargins()
@calcHilightMargins()
# calculate series data point coordinates
#
@ -72,10 +74,13 @@ class Morris.Line extends Morris.Grid
row._y = for y in row.y
if y? then @transY(y) else null
# calculate hover margins
# calculate hilight margins
#
# @private
calcHoverMargins: ->
calcHilightMargins: ->
@hilightMargins = ((r._x + @data[i]._x) / 2 for r, i in @data.slice(1))
hoverCalculateMargins: ->
@hoverMargins = ((r._x + @data[i]._x) / 2 for r, i in @data.slice(1))
# generate paths for series lines
@ -95,8 +100,7 @@ class Morris.Line extends Morris.Grid
draw: ->
@drawXAxis()
@drawSeries()
@drawHover()
@hilight(if @options.hideHover then null else @data.length - 1)
@hilight(if @options.hilightAutoHide then null else @data.length - 1) if @options.hilight
# draw the x-axis labels
#
@ -140,7 +144,7 @@ class Morris.Line extends Morris.Grid
path = @paths[i]
if path isnt null
@r.path(path)
.attr('stroke', @colorForSeries(i))
.attr('stroke', @colorFor(row, i, 'line'))
.attr('stroke-width', @options.lineWidth)
@seriesPoints = ([] for i in [0...@options.ykeys.length])
for i in [@options.ykeys.length-1..0]
@ -149,7 +153,7 @@ class Morris.Line extends Morris.Grid
circle = null
else
circle = @r.circle(row._x, row._y[i], @options.pointSize)
.attr('fill', @pointFillColorForSeries(i) || @colorForSeries(i))
.attr('fill', @colorFor(row, i, 'point'))
.attr('stroke-width', @strokeWidthForSeries(i))
.attr('stroke', @strokeForSeries(i))
@seriesPoints[i].push(circle)
@ -191,61 +195,6 @@ class Morris.Line extends Morris.Grid
else
(coords[i + 1].y - coords[i - 1].y) / (coords[i + 1].x - coords[i - 1].x)
# draw the hover tooltip
#
# @private
drawHover: ->
# hover labels
@hoverHeight = @options.hoverFontSize * 1.5 * (@options.ykeys.length + 1)
@hover = @r.rect(-10, -@hoverHeight / 2 - @options.hoverPaddingY, 20, @hoverHeight + @options.hoverPaddingY * 2, 10)
.attr('fill', @options.hoverFillColor)
.attr('stroke', @options.hoverBorderColor)
.attr('stroke-width', @options.hoverBorderWidth)
.attr('opacity', @options.hoverOpacity)
@xLabel = @r.text(0, (@options.hoverFontSize * 0.75) - @hoverHeight / 2, '')
.attr('fill', @options.hoverLabelColor)
.attr('font-weight', 'bold')
.attr('font-size', @options.hoverFontSize)
@hoverSet = @r.set()
@hoverSet.push(@hover)
@hoverSet.push(@xLabel)
@yLabels = []
for i in [0...@options.ykeys.length]
idx = if @cumulative then (@options.ykeys.length - i - 1) else i
yLabel = @r.text(0, @options.hoverFontSize * 1.5 * (idx + 1.5) - @hoverHeight / 2, '')
.attr('fill', @colorForSeries(i))
.attr('font-size', @options.hoverFontSize)
@yLabels.push(yLabel)
@hoverSet.push(yLabel)
# @private
updateHover: (index) =>
@hoverSet.show()
row = @data[index]
@xLabel.attr('text', row.label)
for y, i in row.y
@yLabels[i].attr('text', "#{@options.labels[i]}: #{@yLabelFormat(y)}")
# recalculate hover box width
maxLabelWidth = Math.max.apply null, (l.getBBox().width for l in @yLabels)
maxLabelWidth = Math.max maxLabelWidth, @xLabel.getBBox().width
@hover.attr 'width', maxLabelWidth + @options.hoverPaddingX * 2
@hover.attr 'x', -@options.hoverPaddingX - maxLabelWidth / 2
# move to y pos
yloc = Math.min.apply null, (y for y in row._y when y isnt null).concat(@bottom)
if yloc > @hoverHeight + @options.hoverPaddingY * 2 + @options.hoverMargin + @top
yloc = yloc - @hoverHeight / 2 - @options.hoverPaddingY - @options.hoverMargin
else
yloc = yloc + @hoverHeight / 2 + @options.hoverPaddingY + @options.hoverMargin
yloc = Math.max @top + @hoverHeight / 2 + @options.hoverPaddingY, yloc
yloc = Math.min @bottom - @hoverHeight / 2 - @options.hoverPaddingY, yloc
xloc = Math.min @right - maxLabelWidth / 2 - @options.hoverPaddingX, @data[index]._x
xloc = Math.max @left + maxLabelWidth / 2 + @options.hoverPaddingX, xloc
@hoverSet.attr 'transform', "t#{xloc},#{yloc}"
# @private
hideHover: ->
@hoverSet.hide()
# @private
hilight: (index) =>
if @prevHilight isnt null and @prevHilight isnt index
@ -256,21 +205,14 @@ class Morris.Line extends Morris.Grid
for i in [0..@seriesPoints.length-1]
if @seriesPoints[i][index]
@seriesPoints[i][index].animate @pointGrow
@updateHover index
@prevHilight = index
if not index?
@hideHover()
# @private
updateHilight: (x) =>
x -= @el.offset().left
for hoverIndex in [0...@hoverMargins.length]
break if @hoverMargins[hoverIndex] > x
@hilight hoverIndex
# @private
colorForSeries: (index) ->
@options.lineColors[index % @options.lineColors.length]
for hilightIndex in [0...@hilightMargins.length]
break if @hilightMargins[hilightIndex] > x
@hilight hilightIndex
# @private
strokeWidthForSeries: (index) ->
@ -280,89 +222,10 @@ class Morris.Line extends Morris.Grid
strokeForSeries: (index) ->
@options.pointStrokeColors[index % @options.pointStrokeColors.length]
# @private
pointFillColorForSeries: (index) ->
@options.pointFillColors[index % @options.pointFillColors.length]
# 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.setMinutes(d.getMinutes() + 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.setSeconds(d.getSeconds() + 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)
"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", "day", "hour",
"30min", "15min", "10min", "5min", "minute",
"30sec", "15sec", "10sec", "5sec", "second"
]
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]

2
morris.css Normal file
View File

@ -0,0 +1,2 @@
div.morris-popup{border-radius:10px;position:absolute;z-index:1000;padding:6px;font:normal 13px/16px Arial,sans-serif;color:#666;background:rgba(255, 255, 255, 0.8);border:solid 2px rgba(230, 230, 230, 0.8);}div.morris-popup h4,div.morris-popup p{font:normal 13px/16px Arial,sans-serif;text-align:center;color:#666;margin:0;}
div.morris-popup h4{font-weight:bold;}

797
morris.js
View File

@ -1,8 +1,8 @@
(function() {
var $, Morris, minutesSpecHelper, secondsSpecHelper,
__slice = [].slice,
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
__slice = [].slice,
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
@ -10,9 +10,46 @@
$ = jQuery;
Morris.EventEmitter = (function() {
Morris.Module = (function() {
function EventEmitter() {}
function Module() {}
Module.extend = function(obj) {
var key, value, _ref;
for (key in obj) {
value = obj[key];
if (key !== 'extended' && key !== 'included') {
this[key] = value;
}
}
if ((_ref = obj.extended) != null) {
_ref.apply(this);
}
return this;
};
Module.include = function(obj) {
var key, value, _ref;
for (key in obj) {
value = obj[key];
if (key !== 'extended' && key !== 'included') {
this.prototype[key] = value;
}
}
return (_ref = obj.included) != null ? _ref.apply(this) : void 0;
};
return Module;
})();
Morris.EventEmitter = (function(_super) {
__extends(EventEmitter, _super);
function EventEmitter() {
return EventEmitter.__super__.constructor.apply(this, arguments);
}
EventEmitter.prototype.on = function(name, handler) {
if (this.handlers == null) {
@ -40,7 +77,7 @@
return EventEmitter;
})();
})(Morris.Module);
Morris.commas = function(num) {
var absnum, intnum, ret, strabsnum;
@ -63,6 +100,135 @@
return (number < 10 ? '0' : '') + number;
};
Morris.labelSeries = function(dmin, dmax, pxwidth, specName, xLabelFormat) {
var d, d0, ddensity, name, ret, s, spec, t, _i, _len, _ref;
ddensity = 200 * (dmax - dmin) / pxwidth;
d0 = new Date(dmin);
spec = Morris.LABEL_SPECS[specName];
if (spec === void 0) {
_ref = Morris.AUTO_LABEL_ORDER;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
name = _ref[_i];
s = Morris.LABEL_SPECS[name];
if (ddensity >= s.span) {
spec = s;
break;
}
}
}
if (spec === void 0) {
spec = Morris.LABEL_SPECS["second"];
}
if (xLabelFormat) {
spec = $.extend({}, spec, {
fmt: xLabelFormat
});
}
d = spec.start(d0);
ret = [];
while ((t = d.getTime()) <= dmax) {
if (t >= dmin) {
ret.push([spec.fmt(d), t]);
}
spec.incr(d);
}
return ret;
};
minutesSpecHelper = function(interval) {
return {
span: interval * 60 * 1000,
start: function(d) {
return new Date(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours());
},
fmt: function(d) {
return "" + (Morris.pad2(d.getHours())) + ":" + (Morris.pad2(d.getMinutes()));
},
incr: function(d) {
return d.setMinutes(d.getMinutes() + interval);
}
};
};
secondsSpecHelper = function(interval) {
return {
span: interval * 1000,
start: function(d) {
return new Date(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes());
},
fmt: function(d) {
return "" + (Morris.pad2(d.getHours())) + ":" + (Morris.pad2(d.getMinutes())) + ":" + (Morris.pad2(d.getSeconds()));
},
incr: function(d) {
return d.setSeconds(d.getSeconds() + interval);
}
};
};
Morris.LABEL_SPECS = {
"decade": {
span: 172800000000,
start: function(d) {
return new Date(d.getFullYear() - d.getFullYear() % 10, 0, 1);
},
fmt: function(d) {
return "" + (d.getFullYear());
},
incr: function(d) {
return d.setFullYear(d.getFullYear() + 10);
}
},
"year": {
span: 17280000000,
start: function(d) {
return new Date(d.getFullYear(), 0, 1);
},
fmt: function(d) {
return "" + (d.getFullYear());
},
incr: function(d) {
return d.setFullYear(d.getFullYear() + 1);
}
},
"month": {
span: 2419200000,
start: function(d) {
return new Date(d.getFullYear(), d.getMonth(), 1);
},
fmt: function(d) {
return "" + (d.getFullYear()) + "-" + (Morris.pad2(d.getMonth() + 1));
},
incr: function(d) {
return d.setMonth(d.getMonth() + 1);
}
},
"day": {
span: 86400000,
start: function(d) {
return new Date(d.getFullYear(), d.getMonth(), d.getDate());
},
fmt: function(d) {
return "" + (d.getFullYear()) + "-" + (Morris.pad2(d.getMonth() + 1)) + "-" + (Morris.pad2(d.getDate()));
},
incr: function(d) {
return 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", "day", "hour", "30min", "15min", "10min", "5min", "minute", "30sec", "15sec", "10sec", "5sec", "second"];
Morris.Grid = (function(_super) {
__extends(Grid, _super);
@ -91,6 +257,9 @@
this.init();
}
this.setData(this.options.data);
if (this.postInit) {
this.postInit();
}
}
Grid.prototype.gridDefaults = {
@ -420,16 +589,183 @@
}
};
Morris.Hover = {
hoverConfigure: function(options) {
return this.hoverOptions = $.extend({}, this.hoverDefaults, options != null ? options : {});
},
hoverInit: function() {
if (this.hoverOptions.enableHover) {
this.hover = this.hoverBuild();
this.hoverBindEvents();
return this.hoverShow(this.hoverOptions.hideHover ? null : this.data.length - 1);
}
},
hoverDefaults: {
enableHover: true,
popupClass: "morris-popup",
hideHover: false,
allowOverflow: false,
pointMargin: 10,
hoverFill: function(index, row) {
return this.hoverFill(index, row);
}
},
hoverBindEvents: function() {
var touchHandler,
_this = this;
this.el.mousemove(function(evt) {
return _this.hoverUpdate(evt.pageX);
});
if (this.hoverOptions.hideHover) {
this.el.mouseout(function(evt) {
return _this.hoverShow(null);
});
}
touchHandler = function(evt) {
var touch;
touch = evt.originalEvent.touches[0] || evt.originalEvent.changedTouches[0];
_this.hoverUpdate(touch.pageX);
return touch;
};
this.el.bind('touchstart', touchHandler);
this.el.bind('touchmove', touchHandler);
this.el.bind('touchend', touchHandler);
this.hover.mousemove(function(evt) {
return evt.stopPropagation();
});
this.hover.mouseout(function(evt) {
return evt.stopPropagation();
});
this.hover.bind('touchstart', function(evt) {
return evt.stopPropagation();
});
this.hover.bind('touchmove', function(evt) {
return evt.stopPropagation();
});
return this.hover.bind('touchend', function(evt) {
return evt.stopPropagation();
});
},
hoverCalculateMargins: function() {
var i;
return this.hoverMargins = (function() {
var _i, _ref, _results;
_results = [];
for (i = _i = 1, _ref = this.data.length; 1 <= _ref ? _i < _ref : _i > _ref; i = 1 <= _ref ? ++_i : --_i) {
_results.push(this.left + i * this.width / this.data.length);
}
return _results;
}).call(this);
},
hoverBuild: function() {
var hover;
hover = $("<div/>");
hover.addClass("" + this.hoverOptions.popupClass + " js-morris-popup");
hover.appendTo(this.el);
hover.hide();
return hover;
},
hoverUpdate: function(x) {
var hoverIndex, _i, _ref;
x -= this.el.offset().left;
for (hoverIndex = _i = 0, _ref = this.hoverMargins.length; 0 <= _ref ? _i < _ref : _i > _ref; hoverIndex = 0 <= _ref ? ++_i : --_i) {
if (this.hoverMargins[hoverIndex] > x) {
break;
}
}
return this.hoverShow(hoverIndex);
},
hoverShow: function(index) {
if (index !== null) {
this.hover.html("");
this.hoverOptions.hoverFill.call(this, index, this.data[index]);
this.hoverPosition(index);
this.fire("hover.show", index);
this.hover.show();
}
if (!(index != null)) {
return this.hoverHide();
}
},
hoverHide: function() {
return this.hover.hide();
},
colorFor: function(row, i, type) {
return "inherit";
},
yLabelFormat: function(label) {
return Morris.commas(label);
},
hoverPosition: function(index) {
var x, y, _ref;
_ref = this.hoverGetPosition(index), x = _ref[0], y = _ref[1];
return this.hover.css({
top: "" + (this.el.offset().top + y) + "px",
left: "" + (this.el.offset().left + x) + "px"
});
},
hoverGetPosition: function(index) {
var miny, row, x, y;
row = this.data[index];
this.hoverWidth = this.hover.outerWidth(true);
this.hoverHeight = this.hover.outerHeight(true);
miny = y = Math.min.apply(null, ((function() {
var _i, _len, _ref, _results;
_ref = row._y;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
y = _ref[_i];
if (y !== null) {
_results.push(y);
}
}
return _results;
})()).concat(this.bottom));
x = row._x - this.hoverWidth / 2;
y = miny;
y = y - this.hoverHeight - this.hoverOptions.pointMargin;
if (!this.hoverOptions.allowOverflow) {
if (x < this.left) {
x = row._x + this.hoverOptions.pointMargin;
} else if (x > this.right - this.hoverWidth) {
x = row._x - this.hoverWidth - this.hoverOptions.pointMargin;
}
y = Math.max(y, this.top);
y = Math.min(y, this.bottom - this.hoverHeight - this.hoverOptions.pointMargin);
if (y - miny < this.hoverWidth + this.hoverOptions.pointMargin) {
y = miny + this.hoverOptions.pointMargin;
}
}
return [x, y];
},
hoverFill: function(index, row) {
var i, xLabel, y, yLabel, _i, _len, _ref, _results;
xLabel = $("<h4/>");
xLabel.text(row.label);
xLabel.appendTo(this.hover);
_ref = row.y;
_results = [];
for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
y = _ref[i];
yLabel = $("<p/>");
yLabel.css("color", this.colorFor(row, i, "hover"));
yLabel.text("" + this.options.labels[i] + ": " + (this.yLabelFormat(y)));
_results.push(yLabel.appendTo(this.hover));
}
return _results;
}
};
Morris.Line = (function(_super) {
__extends(Line, _super);
Line.include(Morris.Hover);
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);
}
@ -445,24 +781,31 @@
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);
this.hoverConfigure(this.options.hoverOptions);
if (this.options.hilight) {
this.prevHilight = null;
this.el.mousemove(function(evt) {
return _this.updateHilight(evt.pageX);
});
if (this.options.hilightAutoHide) {
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);
}
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.postInit = function() {
return this.hoverInit();
};
Line.prototype.defaults = {
@ -472,25 +815,18 @@
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,
hilight: true,
hilightAutoHide: false,
xLabels: 'auto',
xLabelFormat: null
};
Line.prototype.calc = function() {
this.calcPoints();
this.hoverCalculateMargins();
this.generatePaths();
return this.calcHoverMargins();
return this.calcHilightMargins();
};
Line.prototype.calcPoints = function() {
@ -518,7 +854,21 @@
return _results;
};
Line.prototype.calcHoverMargins = function() {
Line.prototype.calcHilightMargins = function() {
var i, r;
return this.hilightMargins = (function() {
var _i, _len, _ref, _results;
_ref = this.data.slice(1);
_results = [];
for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
r = _ref[i];
_results.push((r._x + this.data[i]._x) / 2);
}
return _results;
}).call(this);
};
Line.prototype.hoverCalculateMargins = function() {
var i, r;
return this.hoverMargins = (function() {
var _i, _len, _ref, _results;
@ -567,8 +917,9 @@
Line.prototype.draw = function() {
this.drawXAxis();
this.drawSeries();
this.drawHover();
return this.hilight(this.options.hideHover ? null : this.data.length - 1);
if (this.options.hilight) {
return this.hilight(this.options.hilightAutoHide ? null : this.data.length - 1);
}
};
Line.prototype.drawXAxis = function() {
@ -619,7 +970,7 @@
for (i = _i = _ref = this.options.ykeys.length - 1; _ref <= 0 ? _i <= 0 : _i >= 0; i = _ref <= 0 ? ++_i : --_i) {
path = this.paths[i];
if (path !== null) {
this.r.path(path).attr('stroke', this.colorForSeries(i)).attr('stroke-width', this.options.lineWidth);
this.r.path(path).attr('stroke', this.colorFor(row, i, 'line')).attr('stroke-width', this.options.lineWidth);
}
}
this.seriesPoints = (function() {
@ -641,7 +992,7 @@
if (row._y[i] === null) {
circle = null;
} else {
circle = this.r.circle(row._x, row._y[i], this.options.pointSize).attr('fill', this.pointFillColorForSeries(i) || this.colorForSeries(i)).attr('stroke-width', this.strokeWidthForSeries(i)).attr('stroke', this.strokeForSeries(i));
circle = this.r.circle(row._x, row._y[i], this.options.pointSize).attr('fill', this.colorFor(row, i, 'point')).attr('stroke-width', this.strokeWidthForSeries(i)).attr('stroke', this.strokeForSeries(i));
}
_results1.push(this.seriesPoints[i].push(circle));
}
@ -702,76 +1053,6 @@
return _results;
};
Line.prototype.drawHover = function() {
var i, idx, yLabel, _i, _ref, _results;
this.hoverHeight = this.options.hoverFontSize * 1.5 * (this.options.ykeys.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.options.ykeys.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
idx = this.cumulative ? this.options.ykeys.length - i - 1 : i;
yLabel = this.r.text(0, this.options.hoverFontSize * 1.5 * (idx + 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, l, maxLabelWidth, row, xloc, y, yloc, _i, _len, _ref;
this.hoverSet.show();
row = this.data[index];
this.xLabel.attr('text', row.label);
_ref = row.y;
for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
y = _ref[i];
this.yLabels[i].attr('text', "" + this.options.labels[i] + ": " + (this.yLabelFormat(y)));
}
maxLabelWidth = Math.max.apply(null, (function() {
var _j, _len1, _ref1, _results;
_ref1 = this.yLabels;
_results = [];
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
l = _ref1[_j];
_results.push(l.getBBox().width);
}
return _results;
}).call(this));
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, ((function() {
var _j, _len1, _ref1, _results;
_ref1 = row._y;
_results = [];
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
y = _ref1[_j];
if (y !== null) {
_results.push(y);
}
}
return _results;
})()).concat(this.bottom));
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.data[index]._x);
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) {
@ -787,27 +1068,19 @@
this.seriesPoints[i][index].animate(this.pointGrow);
}
}
this.updateHover(index);
}
this.prevHilight = index;
if (!(index != null)) {
return this.hideHover();
}
return this.prevHilight = index;
};
Line.prototype.updateHilight = function(x) {
var hoverIndex, _i, _ref;
var hilightIndex, _i, _ref;
x -= this.el.offset().left;
for (hoverIndex = _i = 0, _ref = this.hoverMargins.length; 0 <= _ref ? _i < _ref : _i > _ref; hoverIndex = 0 <= _ref ? ++_i : --_i) {
if (this.hoverMargins[hoverIndex] > x) {
for (hilightIndex = _i = 0, _ref = this.hilightMargins.length; 0 <= _ref ? _i < _ref : _i > _ref; hilightIndex = 0 <= _ref ? ++_i : --_i) {
if (this.hilightMargins[hilightIndex] > x) {
break;
}
}
return this.hilight(hoverIndex);
};
Line.prototype.colorForSeries = function(index) {
return this.options.lineColors[index % this.options.lineColors.length];
return this.hilight(hilightIndex);
};
Line.prototype.strokeWidthForSeries = function(index) {
@ -818,143 +1091,20 @@
return this.options.pointStrokeColors[index % this.options.pointStrokeColors.length];
};
Line.prototype.pointFillColorForSeries = function(index) {
return this.options.pointFillColors[index % this.options.pointFillColors.length];
Line.prototype.colorFor = function(row, sidx, type) {
if (typeof this.options.lineColors === 'function') {
return this.options.lineColors.call(this, row, sidx, type);
} else if (type === 'point') {
return this.options.pointFillColors[sidx % this.options.pointFillColors.length] || this.options.lineColors[sidx % this.options.lineColors.length];
} else {
return this.options.lineColors[sidx % this.options.lineColors.length];
}
};
return Line;
})(Morris.Grid);
Morris.labelSeries = function(dmin, dmax, pxwidth, specName, xLabelFormat) {
var d, d0, ddensity, name, ret, s, spec, t, _i, _len, _ref;
ddensity = 200 * (dmax - dmin) / pxwidth;
d0 = new Date(dmin);
spec = Morris.LABEL_SPECS[specName];
if (spec === void 0) {
_ref = Morris.AUTO_LABEL_ORDER;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
name = _ref[_i];
s = Morris.LABEL_SPECS[name];
if (ddensity >= s.span) {
spec = s;
break;
}
}
}
if (spec === void 0) {
spec = Morris.LABEL_SPECS["second"];
}
if (xLabelFormat) {
spec = $.extend({}, spec, {
fmt: xLabelFormat
});
}
d = spec.start(d0);
ret = [];
while ((t = d.getTime()) <= dmax) {
if (t >= dmin) {
ret.push([spec.fmt(d), t]);
}
spec.incr(d);
}
return ret;
};
minutesSpecHelper = function(interval) {
return {
span: interval * 60 * 1000,
start: function(d) {
return new Date(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours());
},
fmt: function(d) {
return "" + (Morris.pad2(d.getHours())) + ":" + (Morris.pad2(d.getMinutes()));
},
incr: function(d) {
return d.setMinutes(d.getMinutes() + interval);
}
};
};
secondsSpecHelper = function(interval) {
return {
span: interval * 1000,
start: function(d) {
return new Date(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes());
},
fmt: function(d) {
return "" + (Morris.pad2(d.getHours())) + ":" + (Morris.pad2(d.getMinutes())) + ":" + (Morris.pad2(d.getSeconds()));
},
incr: function(d) {
return d.setSeconds(d.getSeconds() + interval);
}
};
};
Morris.LABEL_SPECS = {
"decade": {
span: 172800000000,
start: function(d) {
return new Date(d.getFullYear() - d.getFullYear() % 10, 0, 1);
},
fmt: function(d) {
return "" + (d.getFullYear());
},
incr: function(d) {
return d.setFullYear(d.getFullYear() + 10);
}
},
"year": {
span: 17280000000,
start: function(d) {
return new Date(d.getFullYear(), 0, 1);
},
fmt: function(d) {
return "" + (d.getFullYear());
},
incr: function(d) {
return d.setFullYear(d.getFullYear() + 1);
}
},
"month": {
span: 2419200000,
start: function(d) {
return new Date(d.getFullYear(), d.getMonth(), 1);
},
fmt: function(d) {
return "" + (d.getFullYear()) + "-" + (Morris.pad2(d.getMonth() + 1));
},
incr: function(d) {
return d.setMonth(d.getMonth() + 1);
}
},
"day": {
span: 86400000,
start: function(d) {
return new Date(d.getFullYear(), d.getMonth(), d.getDate());
},
fmt: function(d) {
return "" + (d.getFullYear()) + "-" + (Morris.pad2(d.getMonth() + 1)) + "-" + (Morris.pad2(d.getDate()));
},
incr: function(d) {
return 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", "day", "hour", "30min", "15min", "10min", "5min", "minute", "30sec", "15sec", "10sec", "5sec", "second"];
Morris.Area = (function(_super) {
__extends(Area, _super);
@ -1004,7 +1154,7 @@
Area.prototype.fillForSeries = function(i) {
var color;
color = Raphael.rgb2hsl(this.colorForSeries(i));
color = Raphael.rgb2hsl(this.colorFor(this.data[i], i, 'line'));
return Raphael.hsl(color.h, Math.min(255, color.s * 0.75), Math.min(255, color.l * 1.25));
};
@ -1016,12 +1166,15 @@
__extends(Bar, _super);
Bar.include(Morris.Hover);
Bar.prototype.hoverGetPosition = function(index) {
var x, y, _ref;
_ref = Morris.Hover.hoverGetPosition.call(this, index), x = _ref[0], y = _ref[1];
return [x, (this.top + this.bottom) / 2 - this.hoverHeight / 2];
};
function Bar(options) {
this.updateHilight = __bind(this.updateHilight, this);
this.hilight = __bind(this.hilight, this);
this.updateHover = __bind(this.updateHover, this);
if (!(this instanceof Morris.Bar)) {
return new Morris.Bar(options);
}
@ -1031,47 +1184,22 @@
}
Bar.prototype.init = function() {
var touchHandler,
_this = this;
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);
return this.hoverConfigure(this.options.hoverOptions);
};
Bar.prototype.postInit = function() {
return this.hoverInit();
};
Bar.prototype.defaults = {
barSizeRatio: 0.75,
barGap: 3,
barColors: ['#0b62a4', '#7a92a3', '#4da74d', '#afd8f8', '#edc240', '#cb4b4b', '#9440ed'],
hoverPaddingX: 10,
hoverPaddingY: 5,
hoverMargin: 10,
hoverFillColor: '#fff',
hoverBorderColor: '#ccc',
hoverBorderWidth: 2,
hoverOpacity: 0.95,
hoverLabelColor: '#444',
hoverFontSize: 12,
hideHover: false
barColors: ['#0b62a4', '#7a92a3', '#4da74d', '#afd8f8', '#edc240', '#cb4b4b', '#9440ed']
};
Bar.prototype.calc = function() {
this.calcBars();
return this.calcHoverMargins();
return this.hoverCalculateMargins();
};
Bar.prototype.calcBars = function() {
@ -1099,23 +1227,9 @@
return _results;
};
Bar.prototype.calcHoverMargins = function() {
var i;
return this.hoverMargins = (function() {
var _i, _ref, _results;
_results = [];
for (i = _i = 1, _ref = this.data.length; 1 <= _ref ? _i < _ref : _i > _ref; i = 1 <= _ref ? ++_i : --_i) {
_results.push(this.left + i * this.width / this.data.length);
}
return _results;
}).call(this);
};
Bar.prototype.draw = function() {
this.drawXAxis();
this.drawSeries();
this.drawHover();
return this.hilight(this.options.hideHover ? null : this.data.length - 1);
return this.drawSeries();
};
Bar.prototype.drawXAxis = function() {
@ -1177,79 +1291,6 @@
}).call(this);
};
Bar.prototype.drawHover = function() {
var i, yLabel, _i, _ref, _results;
this.hoverHeight = this.options.hoverFontSize * 1.5 * (this.options.ykeys.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.options.ykeys.length; 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('font-size', this.options.hoverFontSize);
this.yLabels.push(yLabel);
_results.push(this.hoverSet.push(yLabel));
}
return _results;
};
Bar.prototype.updateHover = function(index) {
var i, l, maxLabelWidth, row, xloc, y, yloc, _i, _len, _ref;
this.hoverSet.show();
row = this.data[index];
this.xLabel.attr('text', row.label);
_ref = row.y;
for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
y = _ref[i];
this.yLabels[i].attr('fill', this.colorFor(row, i, 'hover'));
this.yLabels[i].attr('text', "" + this.options.labels[i] + ": " + (this.yLabelFormat(y)));
}
maxLabelWidth = Math.max.apply(null, (function() {
var _j, _len1, _ref1, _results;
_ref1 = this.yLabels;
_results = [];
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
l = _ref1[_j];
_results.push(l.getBBox().width);
}
return _results;
}).call(this));
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 = (this.bottom + this.top) / 2;
xloc = Math.min(this.right - maxLabelWidth / 2 - this.options.hoverPaddingX, this.data[index]._x);
xloc = Math.max(this.left + maxLabelWidth / 2 + this.options.hoverPaddingX, xloc);
return this.hoverSet.attr('transform', "t" + xloc + "," + yloc);
};
Bar.prototype.hideHover = function() {
return this.hoverSet.hide();
};
Bar.prototype.hilight = function(index) {
if (index !== null && this.prevHilight !== index) {
this.updateHover(index);
}
this.prevHilight = index;
if (!(index != null)) {
return this.hideHover();
}
};
Bar.prototype.updateHilight = function(x) {
var hoverIndex, _i, _ref;
x -= this.el.offset().left;
for (hoverIndex = _i = 0, _ref = this.hoverMargins.length; 0 <= _ref ? _i < _ref : _i > _ref; hoverIndex = 0 <= _ref ? ++_i : --_i) {
if (this.hoverMargins[hoverIndex] > x) {
break;
}
}
return this.hilight(hoverIndex);
};
Bar.prototype.colorFor = function(row, sidx, type) {
var r, s;
if (typeof this.options.barColors === 'function') {

2
morris.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -16,6 +16,7 @@
},
"devDependencies": {
"grunt-coffee": "~> 0.0.6",
"grunt-mocha": "~> 0.1.7"
"grunt-mocha": "~> 0.1.7",
"grunt-less": "~> 0.1.7"
}
}

View File

@ -29,9 +29,8 @@ describe 'Morris.Line', ->
chart.strokeForSeries(0).should.equal red
chart.strokeWidthForSeries(1).should.equal 2
chart.strokeForSeries(1).should.equal blue
(null == chart.pointFillColorForSeries(0)).should.be
(chart.pointFillColorForSeries(0) || chart.colorForSeries(0)).should.equal chart.colorForSeries(0)
chart.pointFillColorForSeries(1).should.equal red
chart.colorFor(chart.data[0], 0, 'point').should.equal chart.colorFor(chart.data[0], 0, 'line')
chart.colorFor(chart.data[1], 1, 'point').should.equal red
describe 'generating column labels', ->