mirror of
https://github.com/morrisjs/morris.js.git
synced 2024-11-10 21:36:34 +01:00
Merge branch 'master' into event-handling
Conflicts: lib/morris.donut.coffee morris.js morris.min.js
This commit is contained in:
commit
3a2ab2e909
@ -4,5 +4,3 @@ node_js:
|
||||
before_script:
|
||||
- "npm install -g grunt"
|
||||
- "npm install"
|
||||
script:
|
||||
- "grunt coffee mocha"
|
||||
|
19
README.md
19
README.md
@ -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).
|
||||
|
@ -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"
|
||||
|
38
examples/donut-colors.html
Normal file
38
examples/donut-colors.html
Normal 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
30
examples/dst.html
Normal 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>
|
2
grunt.js
2
grunt.js
@ -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');
|
||||
};
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
#
|
||||
#
|
||||
|
@ -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
373
morris.js
@ -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
2
morris.min.js
vendored
File diff suppressed because one or more lines are too long
10
package.json
10
package.json
@ -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"
|
||||
}
|
||||
}
|
||||
|
47
spec/lib/area/area_spec.coffee
Normal file
47
spec/lib/area/area_spec.coffee
Normal 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
|
50
spec/lib/bar/bar_spec.coffee
Normal file
50
spec/lib/bar/bar_spec.coffee
Normal 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
|
@ -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')
|
||||
|
61
spec/lib/donut/donut_spec.coffee
Normal file
61
spec/lib/donut/donut_spec.coffee
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user