Merge branch 'master' into event-handling

Conflicts:
	lib/morris.donut.coffee
	morris.js
	morris.min.js
This commit is contained in:
Marcin Chwedziak 2013-03-31 20:39:32 +02:00
commit 3a2ab2e909
21 changed files with 765 additions and 314 deletions

View File

@ -4,5 +4,3 @@ node_js:
before_script:
- "npm install -g grunt"
- "npm install"
script:
- "grunt coffee mocha"

View File

@ -38,6 +38,25 @@ Once you're all set up, you can compile, minify and run the tests using `grunt`.
## Changelog
### 0.4.1 - 8th February 2013
- Fix goal and event rendering. [#181](https://github.com/oesmith/morris.js/issues/181)
- Don't break when empty data is passed to setData [#142](https://github.com/oesmith/morris.js/issues/142)
- labelColor option for donuts [#159](https://github.com/oesmith/morris.js/issues/159)
### 0.4.0 - 26th January 2013
- Goals and events [#103](https://github.com/oesmith/morris.js/issues/103).
- Bower package manager metadata.
- More flexible formatters [#107](https://github.com/oesmith/morris.js/issues/107).
- Color callbacks.
- Decade intervals for time-axis labels.
- Non-continous line tweaks [#116](https://github.com/oesmith/morris.js/issues/116).
- Stacked bars [#120](https://github.com/oesmith/morris.js/issues/120).
- HTML hover [#134](https://github.com/oesmith/morris.js/issues/134).
- yLabelFormat [#139](https://github.com/oesmith/morris.js/issues/139).
- Disable axes [#114](https://github.com/oesmith/morris.js/issues/114).
### 0.3.3 - 1st November 2012
- **Bar charts!** [#101](https://github.com/oesmith/morris.js/issues/101).

View File

@ -1,7 +1,7 @@
{
"name": "morris.js",
"version": "0.3.3",
"main": "./morris.js",
"version": "0.4.1",
"main": ["./morris.js", "./morris.css"],
"dependencies": {
"jquery": ">= 1.7.2",
"raphael": ">= 2.0"

View File

@ -0,0 +1,38 @@
<!doctype html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="https://raw.github.com/DmitryBaranovskiy/raphael/300aa589f5a0ba7fce667cd62c7cdda0bd5ad904/raphael-min.js"></script>
<script src="../morris.js"></script>
<script src="lib/prettify.js"></script>
<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">
<style>
body { background:#ccc; }
</style>
</head>
<body>
<h1>Donut Chart</h1>
<div id="graph"></div>
<pre id="code" class="prettyprint linenums">
Morris.Donut({
element: 'graph',
data: [
{value: 70, label: 'foo'},
{value: 15, label: 'bar'},
{value: 10, label: 'baz'},
{value: 5, label: 'A really really long label'}
],
backgroundColor: '#ccc',
labelColor: '#060',
colors: [
'#0BA462',
'#39B580',
'#67C69D',
'#95D7BB'
],
formatter: function (x) { return x + "%"}
});
</pre>
</body>

30
examples/dst.html Normal file
View File

@ -0,0 +1,30 @@
<!doctype html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="https://raw.github.com/DmitryBaranovskiy/raphael/300aa589f5a0ba7fce667cd62c7cdda0bd5ad904/raphael-min.js"></script>
<script src="../morris.js"></script>
<script src="lib/prettify.js"></script>
<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>Daylight-savings time</h1>
<div id="graph"></div>
<pre id="code" class="prettyprint linenums">
// This crosses a DST boundary in the UK.
Morris.Area({
element: 'graph',
data: [
{x: '2013-03-30 22:00:00', y: 3, z: 3},
{x: '2013-03-31 00:00:00', y: 2, z: 0},
{x: '2013-03-31 02:00:00', y: 0, z: 2},
{x: '2013-03-31 04:00:00', y: 4, z: 4}
],
xkey: 'x',
ykeys: ['y', 'z'],
labels: ['Y', 'Z']
});
</pre>
</body>

View File

@ -50,7 +50,7 @@ module.exports = function (grunt) {
grunt.loadNpmTasks('grunt-coffee');
grunt.loadNpmTasks('grunt-mocha');
grunt.loadNpmTasks('grunt-less');
grunt.loadNpmTasks('grunt-contrib-less');
grunt.registerTask('default', 'concat coffee less min mocha');
};

View File

@ -26,9 +26,7 @@ class Morris.Area extends Morris.Line
path = @paths[i]
if path isnt null
path = path + "L#{@transX(@xmax)},#{@bottom}L#{@transX(@xmin)},#{@bottom}Z"
@r.path(path)
.attr('fill', @fillForSeries(i))
.attr('stroke-width', 0)
@drawFilledPath(path, @fillForSeries(i))
super()
fillForSeries: (i) ->
@ -37,3 +35,8 @@ class Morris.Area extends Morris.Line
color.h,
Math.min(255, color.s * 0.75),
Math.min(255, color.l * 1.25))
drawFilledPath: (path, fill) ->
@raphael.path(path)
.attr('fill', fill)
.attr('stroke-width', 0)

View File

@ -60,9 +60,7 @@ class Morris.Bar extends Morris.Grid
prevLabelMargin = null
for i in [0...@data.length]
row = @data[@data.length - 1 - i]
label = @r.text(row._x, ypos, row.label)
.attr('font-size', @options.gridTextSize)
.attr('fill', @options.gridTextColor)
label = @drawXAxisLabel(row._x, ypos, row.label)
labelBox = label.getBBox()
# ensure a minimum of `xLabelMargin` pixels between labels, and ensure
# labels don't overflow the container
@ -97,9 +95,7 @@ class Morris.Bar extends Morris.Grid
size = bottom - top
top -= lastTop if @options.stacked
@r.rect(left, top, barWidth, size)
.attr('fill', @colorFor(row, sidx, 'bar'))
.attr('stroke-width', 0)
@drawBar(left, top, barWidth, size, @colorFor(row, sidx, 'bar'))
lastTop += size
else
@ -121,6 +117,7 @@ class Morris.Bar extends Morris.Grid
# hit test - returns the index of the row beneath the given coordinate
#
hitTest: (x, y) ->
return null if @data.length == 0
x = Math.max(Math.min(x, @right), @left)
Math.min(@data.length - 1,
Math.floor((x - @left) / (@width / @data.length)))
@ -164,3 +161,13 @@ class Morris.Bar extends Morris.Grid
"""
x = @left + (index + 0.5) * @width / @data.length
[content, x]
drawXAxisLabel: (xPos, yPos, text) ->
label = @raphael.text(xPos, yPos, text)
.attr('font-size', @options.gridTextSize)
.attr('fill', @options.gridTextColor)
drawBar: (xPos, yPos, width, height, barColor) ->
@raphael.rect(xPos, yPos, width, height)
.attr('fill', barColor)
.attr('stroke-width', 0)

View File

@ -22,6 +22,8 @@ class Morris.Donut extends Morris.EventEmitter
'#052C48'
'#042135'
],
backgroundColor: '#FFFFFF',
labelColor: '#000000',
formatter: Morris.commas
# Create and render a donut chart.
@ -44,6 +46,7 @@ class Morris.Donut extends Morris.EventEmitter
if options.data is undefined or options.data.length is 0
return
@data = options.data
@values = (parseFloat(row.value) for row in @data)
@redraw()
@ -54,14 +57,14 @@ class Morris.Donut extends Morris.EventEmitter
redraw: ->
@el.empty()
@r = new Raphael(@el[0])
@raphael = new Raphael(@el[0])
cx = @el.width() / 2
cy = @el.height() / 2
w = (Math.min(cx, cy) - 10) / 3
total = 0
total += x.value for x in @data
total += value for value in @values
min = 5 / (2 * w)
C = 1.9999 * Math.PI - min * @data.length
@ -69,21 +72,26 @@ class Morris.Donut extends Morris.EventEmitter
last = 0
idx = 0
@segments = []
for d in @data
next = last + min + C * (d.value / total)
seg = new Morris.DonutSegment(cx, cy, w*2, w, last, next, @options.colors[idx % @options.colors.length], d, idx)
seg.render @r
for value, i in @values
next = last + min + C * (value / total)
seg = new Morris.DonutSegment(
cx, cy, w*2, w, last, next,
@options.colors[idx % @options.colors.length],
@options.backgroundColor, @data[i], idx, @raphael)
seg.render()
@segments.push seg
seg.on 'hover', @select
seg.on 'click', @click
last = next
idx += 1
@text1 = @r.text(cx, cy - 10, '').attr('font-size': 15, 'font-weight': 800)
@text2 = @r.text(cx, cy + 10, '').attr('font-size': 14)
max_value = Math.max.apply(null, d.value for d in @data)
@text1 = @drawEmptyDonutLabel(cx, cy - 10, @options.labelColor, 15, 800)
@text2 = @drawEmptyDonutLabel(cx, cy + 10, @options.labelColor, 14)
max_value = Math.max.apply(null, value for value in @values)
idx = 0
for d in @data
if d.value == max_value
for value in @values
if value == max_value
@select idx
break
idx += 1
@ -96,9 +104,10 @@ class Morris.Donut extends Morris.EventEmitter
# Select the segment at the given index.
select: (idx) =>
s.deselect() for s in @segments
if typeof idx is 'number' then segment = @segments[idx] else segment = idx
segment = @segments[idx]
segment.select()
@setLabels segment.data.label, @options.formatter(segment.data.value, segment.data)
row = @data[idx]
@setLabels(row.label, @options.formatter(row.value, row))
# @private
setLabels: (label1, label2) ->
@ -115,17 +124,24 @@ class Morris.Donut extends Morris.EventEmitter
text2scale = Math.min(maxWidth / text2bbox.width, maxHeightBottom / text2bbox.height)
@text2.attr(transform: "S#{text2scale},#{text2scale},#{text2bbox.x + text2bbox.width / 2},#{text2bbox.y}")
drawEmptyDonutLabel: (xPos, yPos, color, fontSize, fontWeight) ->
text = @raphael.text(xPos, yPos, '')
.attr('font-size', fontSize)
.attr('fill', color)
text.attr('font-weight', fontWeight) if fontWeight?
return text
# A segment within a donut chart.
#
# @private
class Morris.DonutSegment extends Morris.EventEmitter
constructor: (@cx, @cy, @inner, @outer, p0, p1, @color, @data, @idx) ->
constructor: (@cx, @cy, @inner, @outer, p0, p1, @color, @backgroundColor, @data, @index, @raphael) ->
@sin_p0 = Math.sin(p0)
@cos_p0 = Math.cos(p0)
@sin_p1 = Math.sin(p1)
@cos_p1 = Math.cos(p1)
@long = if (p1 - p0) > Math.PI then 1 else 0
@is_long = if (p1 - p0) > Math.PI then 1 else 0
@path = @calcSegment(@inner + 3, @inner + @outer - 5)
@selectedPath = @calcSegment(@inner + 3, @inner + @outer)
@hilight = @calcArc(@inner)
@ -142,23 +158,36 @@ class Morris.DonutSegment extends Morris.EventEmitter
[ox0, oy0, ox1, oy1] = @calcArcPoints(r2)
return (
"M#{ix0},#{iy0}" +
"A#{r1},#{r1},0,#{@long},0,#{ix1},#{iy1}" +
"A#{r1},#{r1},0,#{@is_long},0,#{ix1},#{iy1}" +
"L#{ox1},#{oy1}" +
"A#{r2},#{r2},0,#{@long},1,#{ox0},#{oy0}" +
"A#{r2},#{r2},0,#{@is_long},1,#{ox0},#{oy0}" +
"Z")
calcArc: (r) ->
[ix0, iy0, ix1, iy1] = @calcArcPoints(r)
return (
"M#{ix0},#{iy0}" +
"A#{r},#{r},0,#{@long},0,#{ix1},#{iy1}")
"A#{r},#{r},0,#{@is_long},0,#{ix1},#{iy1}")
render: (r) ->
@arc = r.path(@hilight).attr(stroke: @color, 'stroke-width': 2, opacity: 0)
@seg = r.path(@path)
.attr(fill: @color, stroke: 'white', 'stroke-width': 3)
.hover(=> @fire('hover', @))
.click(=> @fire('click', @))
render: ->
@arc = @drawDonutArc(@hilight, @color)
@seg = @drawDonutSegment(
@path,
@color,
@backgroundColor,
=> @fire('hover', @index),
=> @fire('click', @index)
)
drawDonutArc: (path, color) ->
@raphael.path(path)
.attr(stroke: color, 'stroke-width': 2, opacity: 0)
drawDonutSegment: (path, fillColor, strokeColor, hoverFunction, clickFunction) ->
@raphael.path(path)
.attr(fill: fillColor, stroke: strokeColor, 'stroke-width': 3)
.hover(hoverFunction)
.click(clickFunction)
select: =>
unless @selected

View File

@ -17,16 +17,12 @@ class Morris.Grid extends Morris.EventEmitter
@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])
@raphael = new Raphael(@el[0])
# some redraw stuff
@elementWidth = null
@ -98,6 +94,12 @@ class Morris.Grid extends Morris.EventEmitter
# Update the data series and redraw the chart.
#
setData: (data, redraw = true) ->
if !data? or data.length == 0
@data = []
@raphael.clear()
@hover.hide() if @hover?
return
ymax = if @cumulative then 0 else null
ymin = if @cumulative then 0 else null
@ -155,30 +157,9 @@ class Morris.Grid extends Morris.EventEmitter
@xmin -= 1
@xmax += 1
# Compute the vertical range of the graph if desired
if typeof @options.ymax is 'string'
if @options.ymax[0..3] is 'auto'
# use Array.concat to flatten arrays and find the max y value
if @options.ymax.length > 5
@ymax = parseInt(@options.ymax[5..], 10)
@ymax = Math.max(ymax, @ymax) if ymax?
else
@ymax = if ymax? then ymax else 0
else
@ymax = parseInt(@options.ymax, 10)
else
@ymax = @options.ymax
if typeof @options.ymin is 'string'
if @options.ymin[0..3] is 'auto'
if @options.ymin.length > 5
@ymin = parseInt(@options.ymin[5..], 10)
@ymin = Math.min(ymin, @ymin) if ymin?
else
@ymin = if ymin isnt null then ymin else 0
else
@ymin = parseInt(@options.ymin, 10)
else
@ymin = @options.ymin
@ymin = @yboundary('min', ymin)
@ymax = @yboundary('max', ymax)
if @ymin is @ymax
@ymin -= 1 if ymin
@ymax += 1
@ -192,6 +173,21 @@ class Morris.Grid extends Morris.EventEmitter
@dirty = true
@redraw() if redraw
yboundary: (boundaryType, currentValue) ->
boundaryOption = @options["y#{boundaryType}"]
if typeof boundaryOption is 'string'
if boundaryOption[0..3] is 'auto'
if boundaryOption.length > 5
suggestedValue = parseInt(boundaryOption[5..], 10)
return suggestedValue unless currentValue?
Math[boundaryType](currentValue, suggestedValue)
else
if currentValue? then currentValue else 0
else
parseInt(boundaryOption, 10)
else
boundaryOption
_calc: ->
w = @el.width()
h = @el.height()
@ -211,8 +207,8 @@ class Morris.Grid extends Morris.EventEmitter
@measureText(@yAxisFormat(@ymax), @options.gridTextSize).width)
@left += maxYLabelWidth
@bottom -= 1.5 * @options.gridTextSize
@width = @right - @left
@height = @bottom - @top
@width = Math.max(1, @right - @left)
@height = Math.max(1, @bottom - @top)
@dx = @width / (@xmax - @xmin)
@dy = @height / (@ymax - @ymin)
@calc() if @calc
@ -231,51 +227,17 @@ class Morris.Grid extends Morris.EventEmitter
# If you need to re-size your charts, call this method after changing the
# size of the container element.
redraw: ->
@r.clear()
@raphael.clear()
@_calc()
@drawGrid()
@drawGoals()
@drawEvents()
@draw() if @draw
# draw goals horizontal lines
#
drawGoals: ->
for goal, i in @options.goals
@r.path("M#{@left},#{@transY(goal)}H#{@left + @width}")
.attr('stroke', @options.goalLineColors[i % @options.goalLineColors.length])
.attr('stroke-width', @options.goalStrokeWidth)
# draw events vertical lines
drawEvents: ->
for event, i in @events
@r.path("M#{@transX(event)},#{@bottom}V#{@top}")
.attr('stroke', @options.eventLineColors[i % @options.eventLineColors.length])
.attr('stroke-width', @options.eventStrokeWidth)
# draw y axis labels, horizontal lines
#
drawGrid: ->
return if @options.grid is false and @options.axes is false
firstY = @ymin
lastY = @ymax
for lineY in [firstY..lastY] by @yInterval
v = parseFloat(lineY.toFixed(@precision))
y = @transY(v)
if @options.axes
@r.text(@left - @options.padding / 2, y, @yAxisFormat(v))
.attr('font-size', @options.gridTextSize)
.attr('fill', @options.gridTextColor)
.attr('text-anchor', 'end')
if @options.grid
@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)
tt = @raphael.text(100, 100, text).attr('font-size', fontSize)
ret = tt.getBBox()
tt.remove()
ret
@ -297,6 +259,54 @@ class Morris.Grid extends Morris.EventEmitter
if hit?
@hover.update(hit...)
# draw y axis labels, horizontal lines
#
drawGrid: ->
return if @options.grid is false and @options.axes is false
firstY = @ymin
lastY = @ymax
for lineY in [firstY..lastY] by @yInterval
v = parseFloat(lineY.toFixed(@precision))
y = @transY(v)
if @options.axes
@drawYAxisLabel(@left - @options.padding / 2, y, @yAxisFormat(v))
if @options.grid
@drawGridLine("M#{@left},#{y}H#{@left + @width}")
# draw goals horizontal lines
#
drawGoals: ->
for goal, i in @options.goals
color = @options.goalLineColors[i % @options.goalLineColors.length]
@drawGoal(goal, color)
# draw events vertical lines
drawEvents: ->
for event, i in @events
color = @options.eventLineColors[i % @options.eventLineColors.length]
@drawEvent(event, color)
drawGoal: (goal, color) ->
@raphael.path("M#{@left},#{@transY(goal)}H#{@right}")
.attr('stroke', color)
.attr('stroke-width', @options.goalStrokeWidth)
drawEvent: (event, color) ->
@raphael.path("M#{@transX(event)},#{@bottom}V#{@top}")
.attr('stroke', color)
.attr('stroke-width', @options.eventStrokeWidth)
drawYAxisLabel: (xPos, yPos, text) ->
@raphael.text(xPos, yPos, text)
.attr('font-size', @options.gridTextSize)
.attr('fill', @options.gridTextColor)
.attr('text-anchor', 'end')
drawGridLine: (path) ->
@raphael.path(path)
.attr('stroke', @options.gridLineColor)
.attr('stroke-width', @options.gridStrokeWidth)
# Parse a date into a javascript timestamp
#
#

View File

@ -60,6 +60,7 @@ class Morris.Line extends Morris.Grid
# hit test - returns the index of the row beneath the given coordinate
#
hitTest: (x, y) ->
return null if @data.length == 0
# TODO better search algo
for r, index in @data.slice(1)
break if x < (r._x + @data[index]._x) / 2
@ -84,7 +85,7 @@ class Morris.Line extends Morris.Grid
# @private
onHoverOut: =>
if @options.hideHover is 'auto'
@displayHoverForIndex(null)
@displayHoverForRow(null)
# display a hover popup over the given row
#
@ -146,9 +147,7 @@ class Morris.Line extends Morris.Grid
ypos = @bottom + @options.gridTextSize * 1.25
prevLabelMargin = null
drawLabel = (labelText, xpos) =>
label = @r.text(@transX(xpos), ypos, labelText)
.attr('font-size', @options.gridTextSize)
.attr('fill', @options.gridTextColor)
label = @drawXAxisLabel(@transX(xpos), ypos, labelText)
labelBox = label.getBBox()
# ensure a minimum of `xLabelMargin` pixels between labels, and ensure
# labels don't overflow the container
@ -178,17 +177,12 @@ class Morris.Line extends Morris.Grid
for i in [@options.ykeys.length-1..0]
path = @paths[i]
if path isnt null
@r.path(path)
.attr('stroke', @colorFor(row, i, 'line'))
.attr('stroke-width', @options.lineWidth)
@drawLinePath(path, @colorFor(row, i, 'line')) #row isn't available here?
@seriesPoints = ([] for i in [0...@options.ykeys.length])
for i in [@options.ykeys.length-1..0]
for row in @data
if row._y[i]?
circle = @r.circle(row._x, row._y[i], @options.pointSize)
.attr('fill', @colorFor(row, i, 'point'))
.attr('stroke-width', @strokeWidthForSeries(i))
.attr('stroke', @strokeForSeries(i))
circle = @drawLinePoint(row._x, row._y[i], @options.pointSize, @colorFor(row, i, 'point'), i)
else
circle = null
@seriesPoints[i].push(circle)
@ -253,14 +247,6 @@ class Morris.Line extends Morris.Grid
@seriesPoints[i][index].animate @pointGrow
@prevHilight = index
# @private
strokeWidthForSeries: (index) ->
@options.pointWidths[index % @options.pointWidths.length]
# @private
strokeForSeries: (index) ->
@options.pointStrokeColors[index % @options.pointStrokeColors.length]
colorFor: (row, sidx, type) ->
if typeof @options.lineColors is 'function'
@options.lineColors.call(@, row, sidx, type)
@ -269,6 +255,29 @@ class Morris.Line extends Morris.Grid
else
@options.lineColors[sidx % @options.lineColors.length]
drawXAxisLabel: (xPos, yPos, text) ->
@raphael.text(xPos, yPos, text)
.attr('font-size', @options.gridTextSize)
.attr('fill', @options.gridTextColor)
drawLinePath: (path, lineColor) ->
@raphael.path(path)
.attr('stroke', lineColor)
.attr('stroke-width', @options.lineWidth)
drawLinePoint: (xPos, yPos, size, pointColor, lineIndex) ->
@raphael.circle(xPos, yPos, size)
.attr('fill', pointColor)
.attr('stroke-width', @strokeWidthForSeries(lineIndex))
.attr('stroke', @strokeForSeries(lineIndex))
# @private
strokeWidthForSeries: (index) ->
@options.pointWidths[index % @options.pointWidths.length]
# @private
strokeForSeries: (index) ->
@options.pointStrokeColors[index % @options.pointStrokeColors.length]
# generate a series of label, timestamp pairs for x-axis labels
#
@ -304,14 +313,14 @@ 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)
incr: (d) -> d.setUTCMinutes(d.getUTCMinutes() + interval)
# @private
secondsSpecHelper = (interval) ->
span: interval * 1000
start: (d) -> new Date(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes())
fmt: (d) -> "#{Morris.pad2(d.getHours())}:#{Morris.pad2(d.getMinutes())}:#{Morris.pad2(d.getSeconds())}"
incr: (d) -> d.setSeconds(d.getSeconds() + interval)
incr: (d) -> d.setUTCSeconds(d.getUTCSeconds() + interval)
Morris.LABEL_SPECS =
"decade":

373
morris.js
View File

@ -81,13 +81,10 @@
this.el.css('position', 'relative');
}
this.options = $.extend({}, this.gridDefaults, this.defaults || {}, options);
if (this.options.data === void 0 || this.options.data.length === 0) {
return;
}
if (typeof this.options.units === 'string') {
this.options.postUnits = options.units;
}
this.r = new Raphael(this.el[0]);
this.raphael = new Raphael(this.el[0]);
this.elementWidth = null;
this.elementHeight = null;
this.dirty = false;
@ -150,6 +147,14 @@
if (redraw == null) {
redraw = true;
}
if (!(data != null) || data.length === 0) {
this.data = [];
this.raphael.clear();
if (this.hover != null) {
this.hover.hide();
}
return;
}
ymax = this.cumulative ? 0 : null;
ymin = this.cumulative ? 0 : null;
if (this.options.goals.length > 0) {
@ -239,38 +244,8 @@
this.xmin -= 1;
this.xmax += 1;
}
if (typeof this.options.ymax === 'string') {
if (this.options.ymax.slice(0, 4) === 'auto') {
if (this.options.ymax.length > 5) {
this.ymax = parseInt(this.options.ymax.slice(5), 10);
if (ymax != null) {
this.ymax = Math.max(ymax, this.ymax);
}
} else {
this.ymax = ymax != null ? ymax : 0;
}
} else {
this.ymax = parseInt(this.options.ymax, 10);
}
} else {
this.ymax = this.options.ymax;
}
if (typeof this.options.ymin === 'string') {
if (this.options.ymin.slice(0, 4) === 'auto') {
if (this.options.ymin.length > 5) {
this.ymin = parseInt(this.options.ymin.slice(5), 10);
if (ymin != null) {
this.ymin = Math.min(ymin, this.ymin);
}
} else {
this.ymin = ymin !== null ? ymin : 0;
}
} else {
this.ymin = parseInt(this.options.ymin, 10);
}
} else {
this.ymin = this.options.ymin;
}
this.ymin = this.yboundary('min', ymin);
this.ymax = this.yboundary('max', ymax);
if (this.ymin === this.ymax) {
if (ymin) {
this.ymin -= 1;
@ -289,6 +264,32 @@
}
};
Grid.prototype.yboundary = function(boundaryType, currentValue) {
var boundaryOption, suggestedValue;
boundaryOption = this.options["y" + boundaryType];
if (typeof boundaryOption === 'string') {
if (boundaryOption.slice(0, 4) === 'auto') {
if (boundaryOption.length > 5) {
suggestedValue = parseInt(boundaryOption.slice(5), 10);
if (currentValue == null) {
return suggestedValue;
}
return Math[boundaryType](currentValue, suggestedValue);
} else {
if (currentValue != null) {
return currentValue;
} else {
return 0;
}
}
} else {
return parseInt(boundaryOption, 10);
}
} else {
return boundaryOption;
}
};
Grid.prototype._calc = function() {
var h, maxYLabelWidth, w;
w = this.el.width();
@ -306,8 +307,8 @@
this.left += maxYLabelWidth;
this.bottom -= 1.5 * this.options.gridTextSize;
}
this.width = this.right - this.left;
this.height = this.bottom - this.top;
this.width = Math.max(1, this.right - this.left);
this.height = Math.max(1, this.bottom - this.top);
this.dx = this.width / (this.xmax - this.xmin);
this.dy = this.height / (this.ymax - this.ymin);
if (this.calc) {
@ -329,7 +330,7 @@
};
Grid.prototype.redraw = function() {
this.r.clear();
this.raphael.clear();
this._calc();
this.drawGrid();
this.drawGoals();
@ -339,57 +340,12 @@
}
};
Grid.prototype.drawGoals = function() {
var goal, i, _i, _len, _ref, _results;
_ref = this.options.goals;
_results = [];
for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
goal = _ref[i];
_results.push(this.r.path("M" + this.left + "," + (this.transY(goal)) + "H" + (this.left + this.width)).attr('stroke', this.options.goalLineColors[i % this.options.goalLineColors.length]).attr('stroke-width', this.options.goalStrokeWidth));
}
return _results;
};
Grid.prototype.drawEvents = function() {
var event, i, _i, _len, _ref, _results;
_ref = this.events;
_results = [];
for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
event = _ref[i];
_results.push(this.r.path("M" + (this.transX(event)) + "," + this.bottom + "V" + this.top).attr('stroke', this.options.eventLineColors[i % this.options.eventLineColors.length]).attr('stroke-width', this.options.eventStrokeWidth));
}
return _results;
};
Grid.prototype.drawGrid = function() {
var firstY, lastY, lineY, v, y, _i, _ref, _results;
if (this.options.grid === false && this.options.axes === false) {
return;
}
firstY = this.ymin;
lastY = this.ymax;
_results = [];
for (lineY = _i = firstY, _ref = this.yInterval; firstY <= lastY ? _i <= lastY : _i >= lastY; lineY = _i += _ref) {
v = parseFloat(lineY.toFixed(this.precision));
y = this.transY(v);
if (this.options.axes) {
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');
}
if (this.options.grid) {
_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));
} else {
_results.push(void 0);
}
}
return _results;
};
Grid.prototype.measureText = function(text, fontSize) {
var ret, tt;
if (fontSize == null) {
fontSize = 12;
}
tt = this.r.text(100, 100, text).attr('font-size', fontSize);
tt = this.raphael.text(100, 100, text).attr('font-size', fontSize);
ret = tt.getBBox();
tt.remove();
return ret;
@ -415,6 +371,69 @@
}
};
Grid.prototype.drawGrid = function() {
var firstY, lastY, lineY, v, y, _i, _ref, _results;
if (this.options.grid === false && this.options.axes === false) {
return;
}
firstY = this.ymin;
lastY = this.ymax;
_results = [];
for (lineY = _i = firstY, _ref = this.yInterval; firstY <= lastY ? _i <= lastY : _i >= lastY; lineY = _i += _ref) {
v = parseFloat(lineY.toFixed(this.precision));
y = this.transY(v);
if (this.options.axes) {
this.drawYAxisLabel(this.left - this.options.padding / 2, y, this.yAxisFormat(v));
}
if (this.options.grid) {
_results.push(this.drawGridLine("M" + this.left + "," + y + "H" + (this.left + this.width)));
} else {
_results.push(void 0);
}
}
return _results;
};
Grid.prototype.drawGoals = function() {
var color, goal, i, _i, _len, _ref, _results;
_ref = this.options.goals;
_results = [];
for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
goal = _ref[i];
color = this.options.goalLineColors[i % this.options.goalLineColors.length];
_results.push(this.drawGoal(goal, color));
}
return _results;
};
Grid.prototype.drawEvents = function() {
var color, event, i, _i, _len, _ref, _results;
_ref = this.events;
_results = [];
for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
event = _ref[i];
color = this.options.eventLineColors[i % this.options.eventLineColors.length];
_results.push(this.drawEvent(event, color));
}
return _results;
};
Grid.prototype.drawGoal = function(goal, color) {
return this.raphael.path("M" + this.left + "," + (this.transY(goal)) + "H" + this.right).attr('stroke', color).attr('stroke-width', this.options.goalStrokeWidth);
};
Grid.prototype.drawEvent = function(event, color) {
return this.raphael.path("M" + (this.transX(event)) + "," + this.bottom + "V" + this.top).attr('stroke', color).attr('stroke-width', this.options.eventStrokeWidth);
};
Grid.prototype.drawYAxisLabel = function(xPos, yPos, text) {
return this.raphael.text(xPos, yPos, text).attr('font-size', this.options.gridTextSize).attr('fill', this.options.gridTextColor).attr('text-anchor', 'end');
};
Grid.prototype.drawGridLine = function(path) {
return this.raphael.path(path).attr('stroke', this.options.gridLineColor).attr('stroke-width', this.options.gridStrokeWidth);
};
return Grid;
})(Morris.EventEmitter);
@ -632,6 +651,9 @@
Line.prototype.hitTest = function(x, y) {
var index, r, _i, _len, _ref;
if (this.data.length === 0) {
return null;
}
_ref = this.data.slice(1);
for (index = _i = 0, _len = _ref.length; _i < _len; index = ++_i) {
r = _ref[index];
@ -656,7 +678,7 @@
Line.prototype.onHoverOut = function() {
if (this.options.hideHover === 'auto') {
return this.displayHoverForIndex(null);
return this.displayHoverForRow(null);
}
};
@ -749,7 +771,7 @@
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);
label = _this.drawXAxisLabel(_this.transX(xpos), ypos, labelText);
labelBox = label.getBBox();
if ((!(prevLabelMargin != null) || prevLabelMargin >= labelBox.x + labelBox.width) && labelBox.x >= 0 && (labelBox.x + labelBox.width) < _this.el.width()) {
return prevLabelMargin = labelBox.x - _this.options.xLabelMargin;
@ -789,7 +811,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.colorFor(row, i, 'line')).attr('stroke-width', this.options.lineWidth);
this.drawLinePath(path, this.colorFor(row, i, 'line'));
}
}
this.seriesPoints = (function() {
@ -809,7 +831,7 @@
for (_k = 0, _len = _ref2.length; _k < _len; _k++) {
row = _ref2[_k];
if (row._y[i] != null) {
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));
circle = this.drawLinePoint(row._x, row._y[i], this.options.pointSize, this.colorFor(row, i, 'point'), i);
} else {
circle = null;
}
@ -907,14 +929,6 @@
return this.prevHilight = index;
};
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.colorFor = function(row, sidx, type) {
if (typeof this.options.lineColors === 'function') {
return this.options.lineColors.call(this, row, sidx, type);
@ -925,6 +939,26 @@
}
};
Line.prototype.drawXAxisLabel = function(xPos, yPos, text) {
return this.raphael.text(xPos, yPos, text).attr('font-size', this.options.gridTextSize).attr('fill', this.options.gridTextColor);
};
Line.prototype.drawLinePath = function(path, lineColor) {
return this.raphael.path(path).attr('stroke', lineColor).attr('stroke-width', this.options.lineWidth);
};
Line.prototype.drawLinePoint = function(xPos, yPos, size, pointColor, lineIndex) {
return this.raphael.circle(xPos, yPos, size).attr('fill', pointColor).attr('stroke-width', this.strokeWidthForSeries(lineIndex)).attr('stroke', this.strokeForSeries(lineIndex));
};
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];
};
return Line;
})(Morris.Grid);
@ -974,7 +1008,7 @@
return "" + (Morris.pad2(d.getHours())) + ":" + (Morris.pad2(d.getMinutes()));
},
incr: function(d) {
return d.setMinutes(d.getMinutes() + interval);
return d.setUTCMinutes(d.getUTCMinutes() + interval);
}
};
};
@ -989,7 +1023,7 @@
return "" + (Morris.pad2(d.getHours())) + ":" + (Morris.pad2(d.getMinutes())) + ":" + (Morris.pad2(d.getSeconds()));
},
incr: function(d) {
return d.setSeconds(d.getSeconds() + interval);
return d.setUTCSeconds(d.getUTCSeconds() + interval);
}
};
};
@ -1100,7 +1134,7 @@
path = this.paths[i];
if (path !== null) {
path = path + ("L" + (this.transX(this.xmax)) + "," + this.bottom + "L" + (this.transX(this.xmin)) + "," + this.bottom + "Z");
this.r.path(path).attr('fill', this.fillForSeries(i)).attr('stroke-width', 0);
this.drawFilledPath(path, this.fillForSeries(i));
}
}
return Area.__super__.drawSeries.call(this);
@ -1112,6 +1146,10 @@
return Raphael.hsl(color.h, Math.min(255, color.s * 0.75), Math.min(255, color.l * 1.25));
};
Area.prototype.drawFilledPath = function(path, fill) {
return this.raphael.path(path).attr('fill', fill).attr('stroke-width', 0);
};
return Area;
})(Morris.Line);
@ -1200,7 +1238,7 @@
_results = [];
for (i = _i = 0, _ref = this.data.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
row = this.data[this.data.length - 1 - i];
label = this.r.text(row._x, ypos, row.label).attr('font-size', this.options.gridTextSize).attr('fill', this.options.gridTextColor);
label = this.drawXAxisLabel(row._x, ypos, row.label);
labelBox = label.getBBox();
if ((!(prevLabelMargin != null) || prevLabelMargin >= labelBox.x + labelBox.width) && labelBox.x >= 0 && (labelBox.x + labelBox.width) < this.el.width()) {
_results.push(prevLabelMargin = labelBox.x - this.options.xLabelMargin);
@ -1247,7 +1285,7 @@
if (this.options.stacked) {
top -= lastTop;
}
this.r.rect(left, top, barWidth, size).attr('fill', this.colorFor(row, sidx, 'bar')).attr('stroke-width', 0);
this.drawBar(left, top, barWidth, size, this.colorFor(row, sidx, 'bar'));
_results1.push(lastTop += size);
} else {
_results1.push(null);
@ -1280,6 +1318,9 @@
};
Bar.prototype.hitTest = function(x, y) {
if (this.data.length === 0) {
return null;
}
x = Math.max(Math.min(x, this.right), this.left);
return Math.min(this.data.length - 1, Math.floor((x - this.left) / (this.width / this.data.length)));
};
@ -1319,6 +1360,15 @@
return [content, x];
};
Bar.prototype.drawXAxisLabel = function(xPos, yPos, text) {
var label;
return label = this.raphael.text(xPos, yPos, text).attr('font-size', this.options.gridTextSize).attr('fill', this.options.gridTextColor);
};
Bar.prototype.drawBar = function(xPos, yPos, width, height, barColor) {
return this.raphael.rect(xPos, yPos, width, height).attr('fill', barColor).attr('stroke-width', 0);
};
return Bar;
})(Morris.Grid);
@ -1329,6 +1379,8 @@
Donut.prototype.defaults = {
colors: ['#0B62A4', '#3980B5', '#679DC6', '#95BBD7', '#B0CCE1', '#095791', '#095085', '#083E67', '#052C48', '#042135'],
backgroundColor: '#FFFFFF',
labelColor: '#000000',
formatter: Morris.commas
};
@ -1336,6 +1388,8 @@
this.select = __bind(this.select, this);
this.click = __bind(this.click, this);
var row;
if (!(this instanceof Morris.Donut)) {
return new Morris.Donut(options);
}
@ -1352,62 +1406,67 @@
return;
}
this.data = options.data;
this.values = (function() {
var _i, _len, _ref, _results;
_ref = this.data;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
row = _ref[_i];
_results.push(parseFloat(row.value));
}
return _results;
}).call(this);
this.redraw();
}
Donut.prototype.redraw = function() {
var C, cx, cy, d, idx, last, max_value, min, next, seg, total, w, x, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _results;
var C, cx, cy, i, idx, last, max_value, min, next, seg, total, value, w, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _results;
this.el.empty();
this.r = new Raphael(this.el[0]);
this.raphael = new Raphael(this.el[0]);
cx = this.el.width() / 2;
cy = this.el.height() / 2;
w = (Math.min(cx, cy) - 10) / 3;
total = 0;
_ref = this.data;
_ref = this.values;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
x = _ref[_i];
total += x.value;
value = _ref[_i];
total += value;
}
min = 5 / (2 * w);
C = 1.9999 * Math.PI - min * this.data.length;
last = 0;
idx = 0;
this.segments = [];
_ref1 = this.data;
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
d = _ref1[_j];
next = last + min + C * (d.value / total);
seg = new Morris.DonutSegment(cx, cy, w * 2, w, last, next, this.options.colors[idx % this.options.colors.length], d, idx);
seg.render(this.r);
_ref1 = this.values;
for (i = _j = 0, _len1 = _ref1.length; _j < _len1; i = ++_j) {
value = _ref1[i];
next = last + min + C * (value / total);
seg = new Morris.DonutSegment(cx, cy, w * 2, w, last, next, this.options.colors[idx % this.options.colors.length], this.options.backgroundColor, this.data[i], idx, this.raphael);
seg.render();
this.segments.push(seg);
seg.on('hover', this.select);
seg.on('click', this.click);
last = next;
idx += 1;
}
this.text1 = this.r.text(cx, cy - 10, '').attr({
'font-size': 15,
'font-weight': 800
});
this.text2 = this.r.text(cx, cy + 10, '').attr({
'font-size': 14
});
this.text1 = this.drawEmptyDonutLabel(cx, cy - 10, this.options.labelColor, 15, 800);
this.text2 = this.drawEmptyDonutLabel(cx, cy + 10, this.options.labelColor, 14);
max_value = Math.max.apply(null, (function() {
var _k, _len2, _ref2, _results;
_ref2 = this.data;
_ref2 = this.values;
_results = [];
for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
d = _ref2[_k];
_results.push(d.value);
value = _ref2[_k];
_results.push(value);
}
return _results;
}).call(this));
idx = 0;
_ref2 = this.data;
_ref2 = this.values;
_results = [];
for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
d = _ref2[_k];
if (d.value === max_value) {
value = _ref2[_k];
if (value === max_value) {
this.select(idx);
break;
}
@ -1427,19 +1486,16 @@
};
Donut.prototype.select = function(idx) {
var s, segment, _i, _len, _ref;
var row, s, segment, _i, _len, _ref;
_ref = this.segments;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
s = _ref[_i];
s.deselect();
}
if (typeof idx === 'number') {
segment = this.segments[idx];
} else {
segment = idx;
}
segment = this.segments[idx];
segment.select();
return this.setLabels(segment.data.label, this.options.formatter(segment.data.value, segment.data));
row = this.data[idx];
return this.setLabels(row.label, this.options.formatter(row.value, row));
};
Donut.prototype.setLabels = function(label1, label2) {
@ -1468,6 +1524,15 @@
});
};
Donut.prototype.drawEmptyDonutLabel = function(xPos, yPos, color, fontSize, fontWeight) {
var text;
text = this.raphael.text(xPos, yPos, '').attr('font-size', fontSize).attr('fill', color);
if (fontWeight != null) {
text.attr('font-weight', fontWeight);
}
return text;
};
return Donut;
})(Morris.EventEmitter);
@ -1476,14 +1541,16 @@
__extends(DonutSegment, _super);
function DonutSegment(cx, cy, inner, outer, p0, p1, color, data, idx) {
function DonutSegment(cx, cy, inner, outer, p0, p1, color, backgroundColor, data, index, raphael) {
this.cx = cx;
this.cy = cy;
this.inner = inner;
this.outer = outer;
this.color = color;
this.backgroundColor = backgroundColor;
this.data = data;
this.idx = idx;
this.index = index;
this.raphael = raphael;
this.deselect = __bind(this.deselect, this);
this.select = __bind(this.select, this);
@ -1492,7 +1559,7 @@
this.cos_p0 = Math.cos(p0);
this.sin_p1 = Math.sin(p1);
this.cos_p1 = Math.cos(p1);
this.long = (p1 - p0) > Math.PI ? 1 : 0;
this.is_long = (p1 - p0) > Math.PI ? 1 : 0;
this.path = this.calcSegment(this.inner + 3, this.inner + this.outer - 5);
this.selectedPath = this.calcSegment(this.inner + 3, this.inner + this.outer);
this.hilight = this.calcArc(this.inner);
@ -1506,31 +1573,39 @@
var ix0, ix1, iy0, iy1, ox0, ox1, oy0, oy1, _ref, _ref1;
_ref = this.calcArcPoints(r1), ix0 = _ref[0], iy0 = _ref[1], ix1 = _ref[2], iy1 = _ref[3];
_ref1 = this.calcArcPoints(r2), ox0 = _ref1[0], oy0 = _ref1[1], ox1 = _ref1[2], oy1 = _ref1[3];
return ("M" + ix0 + "," + iy0) + ("A" + r1 + "," + r1 + ",0," + this.long + ",0," + ix1 + "," + iy1) + ("L" + ox1 + "," + oy1) + ("A" + r2 + "," + r2 + ",0," + this.long + ",1," + ox0 + "," + oy0) + "Z";
return ("M" + ix0 + "," + iy0) + ("A" + r1 + "," + r1 + ",0," + this.is_long + ",0," + ix1 + "," + iy1) + ("L" + ox1 + "," + oy1) + ("A" + r2 + "," + r2 + ",0," + this.is_long + ",1," + ox0 + "," + oy0) + "Z";
};
DonutSegment.prototype.calcArc = function(r) {
var ix0, ix1, iy0, iy1, _ref;
_ref = this.calcArcPoints(r), ix0 = _ref[0], iy0 = _ref[1], ix1 = _ref[2], iy1 = _ref[3];
return ("M" + ix0 + "," + iy0) + ("A" + r + "," + r + ",0," + this.long + ",0," + ix1 + "," + iy1);
return ("M" + ix0 + "," + iy0) + ("A" + r + "," + r + ",0," + this.is_long + ",0," + ix1 + "," + iy1);
};
DonutSegment.prototype.render = function(r) {
DonutSegment.prototype.render = function() {
var _this = this;
this.arc = r.path(this.hilight).attr({
stroke: this.color,
this.arc = this.drawDonutArc(this.hilight, this.color);
return this.seg = this.drawDonutSegment(this.path, this.color, this.backgroundColor, function() {
return _this.fire('hover', _this.index);
}, function() {
return _this.fire('click', _this.index);
});
};
DonutSegment.prototype.drawDonutArc = function(path, color) {
return this.raphael.path(path).attr({
stroke: color,
'stroke-width': 2,
opacity: 0
});
return this.seg = r.path(this.path).attr({
fill: this.color,
stroke: 'white',
};
DonutSegment.prototype.drawDonutSegment = function(path, fillColor, strokeColor, hoverFunction, clickFunction) {
return this.raphael.path(path).attr({
fill: fillColor,
stroke: strokeColor,
'stroke-width': 3
}).hover(function() {
return _this.fire('hover', _this);
}).click(function() {
return _this.fire('click', _this);
});
}).hover(hoverFunction).click(clickFunction);
};
DonutSegment.prototype.select = function() {

2
morris.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{
"name": "Morris.js",
"version": "0.3.3",
"name": "morris.js",
"version": "0.4.1",
"homepage": "http://oesmith.github.com/morris.js",
"description": "Easy, pretty charts",
"author": {
@ -17,6 +17,10 @@
"devDependencies": {
"grunt-coffee": "~> 0.0.6",
"grunt-mocha": "~> 0.1.7",
"grunt-less": "~> 0.1.7"
"grunt-contrib-less": "~> 0.3.2",
"grunt": "~> 0.3.17"
},
"scripts": {
"test": "./node_modules/.bin/grunt coffee mocha"
}
}

View File

@ -0,0 +1,47 @@
describe 'Morris.Area', ->
describe 'svg structure', ->
defaults =
element: 'graph'
data: [{x: '2012 Q1', y: 1}, {x: '2012 Q2', y: 1}]
lineColors: [ '#0b62a4', '#7a92a3']
gridLineColor: '#aaa'
xkey: 'x'
ykeys: ['y']
labels: ['Y']
it 'should contain a line path for each line', ->
chart = Morris.Area $.extend {}, defaults
$('#graph').find("path[stroke='#0b62a4']").size().should.equal 1
it 'should contain a path with stroke-width 0 for each line', ->
chart = Morris.Area $.extend {}, defaults
$('#graph').find("path[stroke='#0b62a4']").size().should.equal 1
it 'should contain 5 grid lines', ->
chart = Morris.Area $.extend {}, defaults
$('#graph').find("path[stroke='#aaaaaa']").size().should.equal 5
it 'should contain 9 text elements', ->
chart = Morris.Area $.extend {}, defaults
$('#graph').find("text").size().should.equal 9
describe 'svg attributes', ->
defaults =
element: 'graph'
data: [{x: '2012 Q1', y: 1}, {x: '2012 Q2', y: 1}]
xkey: 'x'
ykeys: ['y']
labels: ['Y']
lineColors: [ '#0b62a4', '#7a92a3']
lineWidth: 3
pointWidths: [5]
pointStrokeColors: ['#ffffff']
gridLineColor: '#aaa'
gridStrokeWidth: 0.5
gridTextColor: '#888'
gridTextSize: 12
it 'should have a line with the fill of a modified line color', ->
chart = Morris.Area $.extend {}, defaults
$('#graph').find("path[fill='#2577b5']").size().should.equal 1

View File

@ -0,0 +1,50 @@
describe 'Morris.Bar', ->
describe 'svg structure', ->
defaults =
element: 'graph'
data: [{x: 'foo', y: 2, z: 3}, {x: 'bar', y: 4, z: 6}]
xkey: 'x'
ykeys: ['y', 'z']
labels: ['Y', 'Z']
it 'should contain a rect for each bar', ->
chart = Morris.Bar $.extend {}, defaults
$('#graph').find("rect").size().should.equal 4
it 'should contain 5 grid lines', ->
chart = Morris.Bar $.extend {}, defaults
$('#graph').find("path").size().should.equal 5
it 'should contain 7 text elements', ->
chart = Morris.Bar $.extend {}, defaults
$('#graph').find("text").size().should.equal 7
describe 'svg attributes', ->
defaults =
element: 'graph'
data: [{x: 'foo', y: 2, z: 3}, {x: 'bar', y: 4, z: 6}]
xkey: 'x'
ykeys: ['y', 'z']
labels: ['Y', 'Z']
barColors: [ '#0b62a4', '#7a92a3']
gridLineColor: '#aaa'
gridStrokeWidth: 0.5
gridTextColor: '#888'
gridTextSize: 12
it 'should have a bar with the first default color', ->
chart = Morris.Bar $.extend {}, defaults
$('#graph').find("rect[fill='#0b62a4']").size().should.equal 2
it 'should have a bar with stroke width 0', ->
chart = Morris.Bar $.extend {}, defaults
$('#graph').find("rect[stroke-width='0']").size().should.equal 4
it 'should have text with configured fill color', ->
chart = Morris.Bar $.extend {}, defaults
$('#graph').find("text[fill='#888888']").size().should.equal 7
it 'should have text with configured font size', ->
chart = Morris.Bar $.extend {}, defaults
$('#graph').find("text[font-size='12px']").size().should.equal 7

View File

@ -33,4 +33,4 @@ describe 'Morris.Bar#colorFor', ->
stub.should.have.been.calledWith(
{x:0, y:3, label:'foo'},
{index:1, key:'z', label:'Z'},
'hover')
'hover')

View File

@ -0,0 +1,61 @@
describe 'Morris.Donut', ->
describe 'svg structure', ->
defaults =
element: 'graph'
data: [ {label: 'Jam', value: 25 },
{label: 'Frosted', value: 40 },
{label: 'Custard', value: 25 },
{label: 'Sugar', value: 10 } ]
formatter: (y) -> "#{y}%"
it 'should contain 2 paths for each segment', ->
chart = Morris.Donut $.extend {}, defaults
$('#graph').find("path").size().should.equal 8
it 'should contain 2 text elements for the label', ->
chart = Morris.Donut $.extend {}, defaults
$('#graph').find("text").size().should.equal 2
describe 'svg attributes', ->
defaults =
defaults =
element: 'graph'
data: [ {label: 'Jam', value: 25 },
{label: 'Frosted', value: 40 },
{label: 'Custard', value: 25 },
{label: 'Sugar', value: 10 } ]
formatter: (y) -> "#{y}%"
colors: [ '#0B62A4', '#3980B5', '#679DC6', '#95BBD7']
it 'should have a label with font size 15', ->
chart = Morris.Donut $.extend {}, defaults
$('#graph').find("text[font-size='15px']").size().should.equal 1
it 'should have a label with font size 14', ->
chart = Morris.Donut $.extend {}, defaults
$('#graph').find("text[font-size='14px']").size().should.equal 1
it 'should have a label with font-weight 800', ->
chart = Morris.Donut $.extend {}, defaults
$('#graph').find("text[font-weight='800']").size().should.equal 1
it 'should have 1 paths with fill of first color', ->
chart = Morris.Donut $.extend {}, defaults
$('#graph').find("path[fill='#0b62a4']").size().should.equal 1
it 'should have 1 paths with stroke of first color', ->
chart = Morris.Donut $.extend {}, defaults
$('#graph').find("path[stroke='#0b62a4']").size().should.equal 1
it 'should have a path with white stroke', ->
chart = Morris.Donut $.extend {}, defaults
$('#graph').find("path[stroke='#ffffff']").size().should.equal 4
it 'should have a path with stroke-width 3', ->
chart = Morris.Donut $.extend {}, defaults
$('#graph').find("path[stroke-width='3']").size().should.equal 4
it 'should have a path with stroke-width 2', ->
chart = Morris.Donut $.extend {}, defaults
$('#graph').find("path[stroke-width='2']").size().should.equal 4

View File

@ -12,14 +12,16 @@ describe 'Morris.Grid#setData', ->
my_data.should.deep.equal expected_data
describe 'ymin/ymax', ->
it 'should use a user-specified minimum and maximum value', ->
line = Morris.Line
beforeEach ->
@defaults =
element: 'graph'
data: [{x: 1, y: 1}]
xkey: 'x'
ykeys: ['y', 'z']
labels: ['y', 'z']
it 'should use a user-specified minimum and maximum value', ->
line = Morris.Line $.extend @defaults,
data: [{x: 1, y: 1}]
ymin: 10
ymax: 20
line.ymin.should.equal 10
@ -28,22 +30,16 @@ describe 'Morris.Grid#setData', ->
describe 'auto', ->
it 'should automatically calculate the minimum and maximum value', ->
line = Morris.Line
element: 'graph'
line = Morris.Line $.extend @defaults,
data: [{x: 1, y: 10}, {x: 2, y: 15}, {x: 3, y: null}, {x: 4}]
xkey: 'x'
ykeys: ['y', 'z']
labels: ['y', 'z']
ymin: 'auto'
ymax: 'auto'
line.ymin.should.equal 10
line.ymax.should.equal 15
line = Morris.Line
element: 'graph'
it 'should automatically calculate the minimum and maximum value given no y data', ->
line = Morris.Line $.extend @defaults,
data: [{x: 1}, {x: 2}, {x: 3}, {x: 4}]
xkey: 'x'
ykeys: ['y', 'z']
labels: ['y', 'z']
ymin: 'auto'
ymax: 'auto'
line.ymin.should.equal 0
@ -52,44 +48,32 @@ describe 'Morris.Grid#setData', ->
describe 'auto [n]', ->
it 'should automatically calculate the minimum and maximum value', ->
line = Morris.Line
element: 'graph'
line = Morris.Line $.extend @defaults,
data: [{x: 1, y: 10}, {x: 2, y: 15}, {x: 3, y: null}, {x: 4}]
xkey: 'x'
ykeys: ['y', 'z']
labels: ['y', 'z']
ymin: 'auto 11'
ymax: 'auto 13'
line.ymin.should.equal 10
line.ymax.should.equal 15
line = Morris.Line
element: 'graph'
it 'should automatically calculate the minimum and maximum value given no data', ->
line = Morris.Line $.extend @defaults,
data: [{x: 1}, {x: 2}, {x: 3}, {x: 4}]
xkey: 'x'
ykeys: ['y', 'z']
labels: ['y', 'z']
ymin: 'auto 11'
ymax: 'auto 13'
line.ymin.should.equal 11
line.ymax.should.equal 13
it 'should use a user-specified minimum and maximum value', ->
line = Morris.Line
element: 'graph'
line = Morris.Line $.extend @defaults,
data: [{x: 1, y: 10}, {x: 2, y: 15}, {x: 3, y: null}, {x: 4}]
xkey: 'x'
ykeys: ['y', 'z']
labels: ['y', 'z']
ymin: 'auto 5'
ymax: 'auto 20'
line.ymin.should.equal 5
line.ymax.should.equal 20
line = Morris.Line
element: 'graph'
it 'should use a user-specified minimum and maximum value given no data', ->
line = Morris.Line $.extend @defaults,
data: [{x: 1}, {x: 2}, {x: 3}, {x: 4}]
xkey: 'x'
ykeys: ['y', 'z']
labels: ['y', 'z']
ymin: 'auto 5'
ymax: 'auto 20'
line.ymin.should.equal 5
@ -190,4 +174,26 @@ describe 'Morris.Grid#setData', ->
line.ymax.should == 16
line.data.map((row) -> row.y).should.deep.equal [[13.5], [12], [16], [14]]
it 'should clear the chart when empty data is supplied', ->
line = Morris.Line
element: 'graph',
data: [{x: 2, y: '12'}, {x: 1, y: '13.5'}, {x: 4, y: '14'}, {x: 3, y: '16'}]
xkey: 'x'
ykeys: ['y']
labels: ['y']
line.data.length.should.equal 4
line.setData([])
line.data.length.should.equal 0
line.setData([{x: 2, y: '12'}, {x: 1, y: '13.5'}, {x: 4, y: '14'}, {x: 3, y: '16'}])
line.data.length.should.equal 4
it 'should be able to add data if the chart is initialised with empty data', ->
line = Morris.Line
element: 'graph',
data: []
xkey: 'x'
ykeys: ['y']
labels: ['y']
line.data.length.should.equal 0
line.setData([{x: 2, y: '12'}, {x: 1, y: '13.5'}, {x: 4, y: '14'}, {x: 3, y: '16'}])
line.data.length.should.equal 4

View File

@ -140,3 +140,68 @@ describe 'Morris.Line', ->
testData = [{x: 0, y: null}, {x: 10, y: 10}, {x: 20, y: 0}, {x: 30, y: 10}, {x: 40, y: null}]
path = Morris.Line.createPath(testData, true, 20)
path.should.equal 'M10,10C12.5,7.5,17.5,0,20,0C22.5,0,27.5,7.5,30,10'
describe 'svg structure', ->
defaults =
element: 'graph'
data: [{x: '2012 Q1', y: 1}, {x: '2012 Q2', y: 1}]
lineColors: [ '#0b62a4', '#7a92a3']
xkey: 'x'
ykeys: ['y']
labels: ['dontcare']
it 'should contain a path that represents the line', ->
chart = Morris.Line $.extend {}, defaults
$('#graph').find("path[stroke='#0b62a4']").size().should.equal 1
it 'should contain a circle for each data point', ->
chart = Morris.Line $.extend {}, defaults
$('#graph').find("circle").size().should.equal 2
it 'should contain 5 grid lines', ->
chart = Morris.Line $.extend {}, defaults
$('#graph').find("path[stroke='#aaaaaa']").size().should.equal 5
it 'should contain 9 text elements', ->
chart = Morris.Line $.extend {}, defaults
$('#graph').find("text").size().should.equal 9
describe 'svg attributes', ->
defaults =
element: 'graph'
data: [{x: '2012 Q1', y: 1}, {x: '2012 Q2', y: 1}]
xkey: 'x'
ykeys: ['y', 'z']
labels: ['Y', 'Z']
lineColors: [ '#0b62a4', '#7a92a3']
lineWidth: 3
pointWidths: [5]
pointStrokeColors: ['#ffffff']
gridLineColor: '#aaa'
gridStrokeWidth: 0.5
gridTextColor: '#888'
gridTextSize: 12
it 'should have circles with configured fill color', ->
chart = Morris.Line $.extend {}, defaults
$('#graph').find("circle[fill='#0b62a4']").size().should.equal 2
it 'should have circles with configured stroke width', ->
chart = Morris.Line $.extend {}, defaults
$('#graph').find("circle[stroke-width='5']").size().should.equal 2
it 'should have circles with configured stroke color', ->
chart = Morris.Line $.extend {}, defaults
$('#graph').find("circle[stroke='#ffffff']").size().should.equal 2
it 'should have line with configured line width', ->
chart = Morris.Line $.extend {}, defaults
$('#graph').find("path[stroke-width='3']").size().should.equal 1
it 'should have text with configured font size', ->
chart = Morris.Line $.extend {}, defaults
$('#graph').find("text[font-size='12px']").size().should.equal 9
it 'should have text with configured font size', ->
chart = Morris.Line $.extend {}, defaults
$('#graph').find("text[fill='#888888']").size().should.equal 9