morris-rails 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. data/Gemfile +3 -0
  2. data/Gemfile.lock +97 -0
  3. data/LICENSE +20 -0
  4. data/README.md +58 -0
  5. data/Rakefile +8 -0
  6. data/VERSION +1 -0
  7. data/app/assets/javascripts/morris.area.coffee +42 -0
  8. data/app/assets/javascripts/morris.bar.coffee +165 -0
  9. data/app/assets/javascripts/morris.coffee +42 -0
  10. data/app/assets/javascripts/morris.donut.coffee +182 -0
  11. data/app/assets/javascripts/morris.grid.coffee +392 -0
  12. data/app/assets/javascripts/morris.hover.coffee +41 -0
  13. data/app/assets/javascripts/morris.line.coffee +354 -0
  14. data/app/assets/stylesheets/morris.core.less +27 -0
  15. data/lib/morris-rails/rails.rb +1 -0
  16. data/morris-rails.gemspec +30 -0
  17. data/spec/dummy/README.rdoc +261 -0
  18. data/spec/dummy/Rakefile +7 -0
  19. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  20. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  21. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  22. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  23. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  24. data/spec/dummy/config.ru +4 -0
  25. data/spec/dummy/config/application.rb +65 -0
  26. data/spec/dummy/config/boot.rb +10 -0
  27. data/spec/dummy/config/database.yml +25 -0
  28. data/spec/dummy/config/environment.rb +5 -0
  29. data/spec/dummy/config/environments/development.rb +37 -0
  30. data/spec/dummy/config/environments/production.rb +67 -0
  31. data/spec/dummy/config/environments/test.rb +37 -0
  32. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  33. data/spec/dummy/config/initializers/inflections.rb +15 -0
  34. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  35. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  36. data/spec/dummy/config/initializers/session_store.rb +8 -0
  37. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  38. data/spec/dummy/config/locales/en.yml +5 -0
  39. data/spec/dummy/config/routes.rb +58 -0
  40. data/spec/dummy/db/test.sqlite3 +0 -0
  41. data/spec/dummy/log/test.log +6 -0
  42. data/spec/dummy/public/404.html +26 -0
  43. data/spec/dummy/public/422.html +26 -0
  44. data/spec/dummy/public/500.html +25 -0
  45. data/spec/dummy/public/favicon.ico +0 -0
  46. data/spec/dummy/script/rails +6 -0
  47. data/spec/dummy/tmp/cache/assets/C87/360/sprockets%2F7f2a79129bb3445603eb704843de5397 +0 -0
  48. data/spec/dummy/tmp/cache/assets/D5B/040/sprockets%2F5d0b7809f616213c43a3ba43cbcda91d +0 -0
  49. data/spec/dummy/tmp/cache/assets/DAE/5D0/sprockets%2Fae427ff9e5b67de97e2ad285c5b623b6 +0 -0
  50. data/spec/morris-rails_spec.rb +12 -0
  51. data/spec/spec_helper.rb +16 -0
  52. metadata +271 -0
@@ -0,0 +1,41 @@
1
+ class Morris.Hover
2
+ # Displays contextual information in a floating HTML div.
3
+
4
+ @defaults:
5
+ class: 'morris-hover morris-default-style'
6
+
7
+ constructor: (options = {}) ->
8
+ @options = $.extend {}, Morris.Hover.defaults, options
9
+ @el = $ "<div class='#{@options.class}'></div>"
10
+ @el.hide()
11
+ @options.parent.append(@el)
12
+
13
+ update: (html, x, y) ->
14
+ @html(html)
15
+ @show()
16
+ @moveTo(x, y)
17
+
18
+ html: (content) ->
19
+ @el.html(content)
20
+
21
+ moveTo: (x, y) ->
22
+ parentWidth = @options.parent.innerWidth()
23
+ parentHeight = @options.parent.innerHeight()
24
+ hoverWidth = @el.outerWidth()
25
+ hoverHeight = @el.outerHeight()
26
+ left = Math.min(Math.max(0, x - hoverWidth / 2), parentWidth - hoverWidth)
27
+ if y?
28
+ top = y - hoverHeight - 10
29
+ if top < 0
30
+ top = y + 10
31
+ if top + hoverHeight > parentHeight
32
+ top = parentHeight / 2 - hoverHeight / 2
33
+ else
34
+ top = parentHeight / 2 - hoverHeight / 2
35
+ @el.css(left: left + "px", top: top + "px")
36
+
37
+ show: ->
38
+ @el.show()
39
+
40
+ hide: ->
41
+ @el.hide()
@@ -0,0 +1,354 @@
1
+ class Morris.Line extends Morris.Grid
2
+ # Initialise the graph.
3
+ #
4
+ constructor: (options) ->
5
+ return new Morris.Line(options) unless (@ instanceof Morris.Line)
6
+ super(options)
7
+
8
+ init: ->
9
+ # Some instance variables for later
10
+ @pointGrow = Raphael.animation r: @options.pointSize + 3, 25, 'linear'
11
+ @pointShrink = Raphael.animation r: @options.pointSize, 25, 'linear'
12
+
13
+ if @options.hideHover isnt 'always'
14
+ @hover = new Morris.Hover(parent: @el)
15
+ @on('hovermove', @onHoverMove)
16
+ @on('hoverout', @onHoverOut)
17
+
18
+ # Default configuration
19
+ #
20
+ defaults:
21
+ lineWidth: 3
22
+ pointSize: 4
23
+ lineColors: [
24
+ '#0b62a4'
25
+ '#7A92A3'
26
+ '#4da74d'
27
+ '#afd8f8'
28
+ '#edc240'
29
+ '#cb4b4b'
30
+ '#9440ed'
31
+ ]
32
+ pointWidths: [1]
33
+ pointStrokeColors: ['#ffffff']
34
+ pointFillColors: []
35
+ smooth: true
36
+ xLabels: 'auto'
37
+ xLabelFormat: null
38
+ xLabelMargin: 50
39
+ continuousLine: true
40
+ hideHover: false
41
+
42
+ # Do any size-related calculations
43
+ #
44
+ # @private
45
+ calc: ->
46
+ @calcPoints()
47
+ @generatePaths()
48
+
49
+ # calculate series data point coordinates
50
+ #
51
+ # @private
52
+ calcPoints: ->
53
+ for row in @data
54
+ row._x = @transX(row.x)
55
+ row._y = for y in row.y
56
+ if y? then @transY(y) else y
57
+ row._ymax = Math.min.apply(null, [@bottom].concat(y for y in row._y when y?))
58
+
59
+ # hit test - returns the index of the row beneath the given coordinate
60
+ #
61
+ hitTest: (x, y) ->
62
+ return null if @data.length == 0
63
+ # TODO better search algo
64
+ for r, index in @data.slice(1)
65
+ break if x < (r._x + @data[index]._x) / 2
66
+ index
67
+
68
+ # hover movement event handler
69
+ #
70
+ # @private
71
+ onHoverMove: (x, y) =>
72
+ index = @hitTest(x, y)
73
+ @displayHoverForRow(index)
74
+
75
+ # hover out event handler
76
+ #
77
+ # @private
78
+ onHoverOut: =>
79
+ if @options.hideHover is 'auto'
80
+ @displayHoverForRow(null)
81
+
82
+ # display a hover popup over the given row
83
+ #
84
+ # @private
85
+ displayHoverForRow: (index) ->
86
+ if index?
87
+ @hover.update(@hoverContentForRow(index)...)
88
+ @hilight(index)
89
+ else
90
+ @hover.hide()
91
+ @hilight()
92
+
93
+ # hover content for a point
94
+ #
95
+ # @private
96
+ hoverContentForRow: (index) ->
97
+ row = @data[index]
98
+ if typeof @options.hoverCallback is 'function'
99
+ content = @options.hoverCallback(index, @options)
100
+ else
101
+ content = "<div class='morris-hover-row-label'>#{row.label}</div>"
102
+ for y, j in row.y
103
+ content += """
104
+ <div class='morris-hover-point' style='color: #{@colorFor(row, j, 'label')}'>
105
+ #{@options.labels[j]}:
106
+ #{@yLabelFormat(y)}
107
+ </div>
108
+ """
109
+ [content, row._x, row._ymax]
110
+
111
+
112
+ # generate paths for series lines
113
+ #
114
+ # @private
115
+ generatePaths: ->
116
+ @paths = for i in [0...@options.ykeys.length]
117
+ smooth = @options.smooth is true or @options.ykeys[i] in @options.smooth
118
+ coords = ({x: r._x, y: r._y[i]} for r in @data when r._y[i] isnt undefined)
119
+ coords = (c for c in coords when c.y isnt null) if @options.continuousLine
120
+
121
+ if coords.length > 1
122
+ Morris.Line.createPath coords, smooth, @bottom
123
+ else
124
+ null
125
+
126
+ # Draws the line chart.
127
+ #
128
+ draw: ->
129
+ @drawXAxis() if @options.axes
130
+ @drawSeries()
131
+ if @options.hideHover is false
132
+ @displayHoverForRow(@data.length - 1)
133
+
134
+ # draw the x-axis labels
135
+ #
136
+ # @private
137
+ drawXAxis: ->
138
+ # draw x axis labels
139
+ ypos = @bottom + @options.gridTextSize * 1.25
140
+ prevLabelMargin = null
141
+ drawLabel = (labelText, xpos) =>
142
+ label = @drawXAxisLabel(@transX(xpos), ypos, labelText)
143
+ labelBox = label.getBBox()
144
+ # ensure a minimum of `xLabelMargin` pixels between labels, and ensure
145
+ # labels don't overflow the container
146
+ if (not prevLabelMargin? or prevLabelMargin >= labelBox.x + labelBox.width) and
147
+ labelBox.x >= 0 and (labelBox.x + labelBox.width) < @el.width()
148
+ prevLabelMargin = labelBox.x - @options.xLabelMargin
149
+ else
150
+ label.remove()
151
+ if @options.parseTime
152
+ if @data.length == 1 and @options.xLabels == 'auto'
153
+ # where there's only one value in the series, we can't make a
154
+ # sensible guess for an x labelling scheme, so just use the original
155
+ # column label
156
+ labels = [[@data[0].label, @data[0].x]]
157
+ else
158
+ labels = Morris.labelSeries(@xmin, @xmax, @width, @options.xLabels, @options.xLabelFormat)
159
+ else
160
+ labels = ([row.label, row.x] for row in @data)
161
+ labels.reverse()
162
+ for l in labels
163
+ drawLabel(l[0], l[1])
164
+
165
+ # draw the data series
166
+ #
167
+ # @private
168
+ drawSeries: ->
169
+ for i in [@options.ykeys.length-1..0]
170
+ path = @paths[i]
171
+ if path isnt null
172
+ @drawLinePath(path, @colorFor(row, i, 'line')) #row isn't available here?
173
+ @seriesPoints = ([] for i in [0...@options.ykeys.length])
174
+ for i in [@options.ykeys.length-1..0]
175
+ for row in @data
176
+ if row._y[i]?
177
+ circle = @drawLinePoint(row._x, row._y[i], @options.pointSize, @colorFor(row, i, 'point'), i)
178
+ else
179
+ circle = null
180
+ @seriesPoints[i].push(circle)
181
+
182
+ # create a path for a data series
183
+ #
184
+ # @private
185
+ @createPath: (coords, smooth, bottom) ->
186
+ path = ""
187
+ grads = Morris.Line.gradients(coords) if smooth
188
+
189
+ prevCoord = {y: null}
190
+ for coord, i in coords
191
+ if coord.y?
192
+ if prevCoord.y?
193
+ if smooth
194
+ g = grads[i]
195
+ lg = grads[i - 1]
196
+ ix = (coord.x - prevCoord.x) / 4
197
+ x1 = prevCoord.x + ix
198
+ y1 = Math.min(bottom, prevCoord.y + ix * lg)
199
+ x2 = coord.x - ix
200
+ y2 = Math.min(bottom, coord.y - ix * g)
201
+ path += "C#{x1},#{y1},#{x2},#{y2},#{coord.x},#{coord.y}"
202
+ else
203
+ path += "L#{coord.x},#{coord.y}"
204
+ else
205
+ if not smooth or grads[i]?
206
+ path += "M#{coord.x},#{coord.y}"
207
+ prevCoord = coord
208
+ return path
209
+
210
+ # calculate a gradient at each point for a series of points
211
+ #
212
+ # @private
213
+ @gradients: (coords) ->
214
+ grad = (a, b) -> (a.y - b.y) / (a.x - b.x)
215
+ for coord, i in coords
216
+ if coord.y?
217
+ nextCoord = coords[i + 1] or {y: null}
218
+ prevCoord = coords[i - 1] or {y: null}
219
+ if prevCoord.y? and nextCoord.y?
220
+ grad(prevCoord, nextCoord)
221
+ else if prevCoord.y?
222
+ grad(prevCoord, coord)
223
+ else if nextCoord.y?
224
+ grad(coord, nextCoord)
225
+ else
226
+ null
227
+ else
228
+ null
229
+
230
+ # @private
231
+ hilight: (index) =>
232
+ if @prevHilight isnt null and @prevHilight isnt index
233
+ for i in [0..@seriesPoints.length-1]
234
+ if @seriesPoints[i][@prevHilight]
235
+ @seriesPoints[i][@prevHilight].animate @pointShrink
236
+ if index isnt null and @prevHilight isnt index
237
+ for i in [0..@seriesPoints.length-1]
238
+ if @seriesPoints[i][index]
239
+ @seriesPoints[i][index].animate @pointGrow
240
+ @prevHilight = index
241
+
242
+ colorFor: (row, sidx, type) ->
243
+ if typeof @options.lineColors is 'function'
244
+ @options.lineColors.call(@, row, sidx, type)
245
+ else if type is 'point'
246
+ @options.pointFillColors[sidx % @options.pointFillColors.length] || @options.lineColors[sidx % @options.lineColors.length]
247
+ else
248
+ @options.lineColors[sidx % @options.lineColors.length]
249
+
250
+ drawXAxisLabel: (xPos, yPos, text) ->
251
+ @raphael.text(xPos, yPos, text)
252
+ .attr('font-size', @options.gridTextSize)
253
+ .attr('fill', @options.gridTextColor)
254
+
255
+ drawLinePath: (path, lineColor) ->
256
+ @raphael.path(path)
257
+ .attr('stroke', lineColor)
258
+ .attr('stroke-width', @options.lineWidth)
259
+
260
+ drawLinePoint: (xPos, yPos, size, pointColor, lineIndex) ->
261
+ @raphael.circle(xPos, yPos, size)
262
+ .attr('fill', pointColor)
263
+ .attr('stroke-width', @strokeWidthForSeries(lineIndex))
264
+ .attr('stroke', @strokeForSeries(lineIndex))
265
+
266
+ # @private
267
+ strokeWidthForSeries: (index) ->
268
+ @options.pointWidths[index % @options.pointWidths.length]
269
+
270
+ # @private
271
+ strokeForSeries: (index) ->
272
+ @options.pointStrokeColors[index % @options.pointStrokeColors.length]
273
+
274
+ # generate a series of label, timestamp pairs for x-axis labels
275
+ #
276
+ # @private
277
+ Morris.labelSeries = (dmin, dmax, pxwidth, specName, xLabelFormat) ->
278
+ ddensity = 200 * (dmax - dmin) / pxwidth # seconds per `margin` pixels
279
+ d0 = new Date(dmin)
280
+ spec = Morris.LABEL_SPECS[specName]
281
+ # if the spec doesn't exist, search for the closest one in the list
282
+ if spec is undefined
283
+ for name in Morris.AUTO_LABEL_ORDER
284
+ s = Morris.LABEL_SPECS[name]
285
+ if ddensity >= s.span
286
+ spec = s
287
+ break
288
+ # if we run out of options, use second-intervals
289
+ if spec is undefined
290
+ spec = Morris.LABEL_SPECS["second"]
291
+ # check if there's a user-defined formatting function
292
+ if xLabelFormat
293
+ spec = $.extend({}, spec, {fmt: xLabelFormat})
294
+ # calculate labels
295
+ d = spec.start(d0)
296
+ ret = []
297
+ while (t = d.getTime()) <= dmax
298
+ if t >= dmin
299
+ ret.push [spec.fmt(d), t]
300
+ spec.incr(d)
301
+ return ret
302
+
303
+ # @private
304
+ minutesSpecHelper = (interval) ->
305
+ span: interval * 60 * 1000
306
+ start: (d) -> new Date(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours())
307
+ fmt: (d) -> "#{Morris.pad2(d.getHours())}:#{Morris.pad2(d.getMinutes())}"
308
+ incr: (d) -> d.setMinutes(d.getMinutes() + interval)
309
+
310
+ # @private
311
+ secondsSpecHelper = (interval) ->
312
+ span: interval * 1000
313
+ start: (d) -> new Date(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes())
314
+ fmt: (d) -> "#{Morris.pad2(d.getHours())}:#{Morris.pad2(d.getMinutes())}:#{Morris.pad2(d.getSeconds())}"
315
+ incr: (d) -> d.setSeconds(d.getSeconds() + interval)
316
+
317
+ Morris.LABEL_SPECS =
318
+ "decade":
319
+ span: 172800000000 # 10 * 365 * 24 * 60 * 60 * 1000
320
+ start: (d) -> new Date(d.getFullYear() - d.getFullYear() % 10, 0, 1)
321
+ fmt: (d) -> "#{d.getFullYear()}"
322
+ incr: (d) -> d.setFullYear(d.getFullYear() + 10)
323
+ "year":
324
+ span: 17280000000 # 365 * 24 * 60 * 60 * 1000
325
+ start: (d) -> new Date(d.getFullYear(), 0, 1)
326
+ fmt: (d) -> "#{d.getFullYear()}"
327
+ incr: (d) -> d.setFullYear(d.getFullYear() + 1)
328
+ "month":
329
+ span: 2419200000 # 28 * 24 * 60 * 60 * 1000
330
+ start: (d) -> new Date(d.getFullYear(), d.getMonth(), 1)
331
+ fmt: (d) -> "#{d.getFullYear()}-#{Morris.pad2(d.getMonth() + 1)}"
332
+ incr: (d) -> d.setMonth(d.getMonth() + 1)
333
+ "day":
334
+ span: 86400000 # 24 * 60 * 60 * 1000
335
+ start: (d) -> new Date(d.getFullYear(), d.getMonth(), d.getDate())
336
+ fmt: (d) -> "#{d.getFullYear()}-#{Morris.pad2(d.getMonth() + 1)}-#{Morris.pad2(d.getDate())}"
337
+ incr: (d) -> d.setDate(d.getDate() + 1)
338
+ "hour": minutesSpecHelper(60)
339
+ "30min": minutesSpecHelper(30)
340
+ "15min": minutesSpecHelper(15)
341
+ "10min": minutesSpecHelper(10)
342
+ "5min": minutesSpecHelper(5)
343
+ "minute": minutesSpecHelper(1)
344
+ "30sec": secondsSpecHelper(30)
345
+ "15sec": secondsSpecHelper(15)
346
+ "10sec": secondsSpecHelper(10)
347
+ "5sec": secondsSpecHelper(5)
348
+ "second": secondsSpecHelper(1)
349
+
350
+ Morris.AUTO_LABEL_ORDER = [
351
+ "decade", "year", "month", "day", "hour",
352
+ "30min", "15min", "10min", "5min", "minute",
353
+ "30sec", "15sec", "10sec", "5sec", "second"
354
+ ]
@@ -0,0 +1,27 @@
1
+ .morris-hover {
2
+ position: absolute;
3
+ z-index: 1000;
4
+
5
+ &.morris-default-style {
6
+ border-radius: 10px;
7
+ padding: 6px;
8
+ color: #666;
9
+ background: rgba(255, 255, 255, 0.8);
10
+ border: solid 2px rgba(230, 230, 230, 0.8);
11
+
12
+ font-family: sans-serif;
13
+ font-size: 12px;
14
+ text-align: center;
15
+
16
+ .morris-hover-row-label {
17
+ font-weight: bold;
18
+ margin: 0.25em 0;
19
+ }
20
+
21
+ .morris-hover-point {
22
+ white-space: nowrap;
23
+ margin: 0.1em 0;
24
+ }
25
+ }
26
+
27
+ }
@@ -0,0 +1 @@
1
+ require 'morris-rails/rails/engine'
@@ -0,0 +1,30 @@
1
+ require 'pathname'
2
+
3
+ Gem::Specification.new do |s|
4
+
5
+ # Variables
6
+ s.name = Pathname.new(__FILE__).basename('.gemspec').to_s
7
+ s.author = 'Ryan Scott Lewis'
8
+ s.email = 'ryan@rynet.us'
9
+ s.summary = 'morris.js for the Rails asset pipeline.'
10
+ s.description = s.summary
11
+ s.license = 'MIT'
12
+
13
+ # Dependencies
14
+ s.add_dependency 'version', '~> 1.0'
15
+ s.add_dependency 'railties', '>= 3.2.0', '< 5.0'
16
+ s.add_dependency 'jquery-rails', '~> 2.0'
17
+ s.add_dependency 'raphaeljs-rails', '~> 1.0'
18
+ s.add_development_dependency 'rake', '~> 10.0'
19
+ s.add_development_dependency 'fancy_logger', '~> 0.1'
20
+ s.add_development_dependency 'rspec', '~> 2.13'
21
+ s.add_development_dependency 'fuubar', '~> 1.1'
22
+
23
+ # Pragmatically set variables
24
+ s.homepage = "http://github.com/RyanScottLewis/#{s.name}"
25
+ s.version = Pathname.glob('VERSION*').first.read rescue '0.0.0'
26
+ s.require_paths = ['lib']
27
+ s.files = Dir['{{Rake,Gem}file{.lock,},README*,VERSION,LICENSE,*.gemspec,lib/morris-rails**/*.rb,spec/**/*.rb,app/**/*}']
28
+ s.test_files = Dir['{examples,spec,test}/**/*']
29
+
30
+ end