morris.bar.coffee 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. class Morris.Bar extends Morris.Grid
  2. constructor: (options) ->
  3. return new Morris.Bar(options) unless (@ instanceof Morris.Bar)
  4. super($.extend {}, options, parseTime: false)
  5. init: ->
  6. @cumulative = @options.stacked
  7. if @options.hideHover isnt 'always'
  8. @hover = new Morris.Hover(parent: @el)
  9. @on('hovermove', @onHoverMove)
  10. @on('hoverout', @onHoverOut)
  11. @on('gridclick', @onGridClick)
  12. # Default configuration
  13. #
  14. defaults:
  15. barSizeRatio: 0.75
  16. barGap: 3
  17. barColors: [
  18. '#0b62a4'
  19. '#7a92a3'
  20. '#4da74d'
  21. '#afd8f8'
  22. '#edc240'
  23. '#cb4b4b'
  24. '#9440ed'
  25. ],
  26. barOpacity: 1.0
  27. barRadius: [0, 0, 0, 0]
  28. xLabelMargin: 50
  29. # Do any size-related calculations
  30. #
  31. # @private
  32. calc: ->
  33. @calcBars()
  34. if @options.hideHover is false
  35. @hover.update(@hoverContentForRow(@data.length - 1)...)
  36. # calculate series data bars coordinates and sizes
  37. #
  38. # @private
  39. calcBars: ->
  40. for row, idx in @data
  41. row._x = @left + @width * (idx + 0.5) / @data.length
  42. row._y = for y in row.y
  43. if y? then @transY(y) else null
  44. # Draws the bar chart.
  45. #
  46. draw: ->
  47. @drawXAxis() if @options.axes in [true, 'both', 'x']
  48. @drawSeries()
  49. # draw the x-axis labels
  50. #
  51. # @private
  52. drawXAxis: ->
  53. # draw x axis labels
  54. ypos = @bottom + (@options.xAxisLabelTopPadding || @options.padding / 2)
  55. prevLabelMargin = null
  56. prevAngleMargin = null
  57. for i in [0...@data.length]
  58. row = @data[@data.length - 1 - i]
  59. label = @drawXAxisLabel(row._x, ypos, row.label)
  60. textBox = label.getBBox()
  61. label.transform("r#{-@options.xLabelAngle}")
  62. labelBox = label.getBBox()
  63. label.transform("t0,#{labelBox.height / 2}...")
  64. if @options.xLabelAngle != 0
  65. offset = -0.5 * textBox.width *
  66. Math.cos(@options.xLabelAngle * Math.PI / 180.0)
  67. label.transform("t#{offset},0...")
  68. # try to avoid overlaps
  69. if (not prevLabelMargin? or
  70. prevLabelMargin >= labelBox.x + labelBox.width or
  71. prevAngleMargin? and prevAngleMargin >= labelBox.x) and
  72. labelBox.x >= 0 and (labelBox.x + labelBox.width) < @el.width()
  73. if @options.xLabelAngle != 0
  74. margin = 1.25 * @options.gridTextSize /
  75. Math.sin(@options.xLabelAngle * Math.PI / 180.0)
  76. prevAngleMargin = labelBox.x - margin
  77. prevLabelMargin = labelBox.x - @options.xLabelMargin
  78. else
  79. label.remove()
  80. # draw the data series
  81. #
  82. # @private
  83. drawSeries: ->
  84. groupWidth = @width / @options.data.length
  85. numBars = if @options.stacked then 1 else @options.ykeys.length
  86. barWidth = (groupWidth * @options.barSizeRatio - @options.barGap * (numBars - 1)) / numBars
  87. barWidth = Math.min(barWidth, @options.barSize) if @options.barSize
  88. spaceLeft = groupWidth - barWidth * numBars - @options.barGap * (numBars - 1)
  89. leftPadding = spaceLeft / 2
  90. zeroPos = if @ymin <= 0 and @ymax >= 0 then @transY(0) else null
  91. @bars = for row, idx in @data
  92. lastTop = 0
  93. for ypos, sidx in row._y
  94. if ypos != null
  95. if zeroPos
  96. top = Math.min(ypos, zeroPos)
  97. bottom = Math.max(ypos, zeroPos)
  98. else
  99. top = ypos
  100. bottom = @bottom
  101. left = @left + idx * groupWidth + leftPadding
  102. left += sidx * (barWidth + @options.barGap) unless @options.stacked
  103. size = bottom - top
  104. if @options.verticalGridCondition and @options.verticalGridCondition(row.x)
  105. @drawBar(@left + idx * groupWidth, @top, groupWidth, Math.abs(@top - @bottom), @options.verticalGridColor, @options.verticalGridOpacity, @options.barRadius)
  106. top -= lastTop if @options.stacked
  107. @drawBar(left, top, barWidth, size, @colorFor(row, sidx, 'bar'),
  108. @options.barOpacity, @options.barRadius)
  109. lastTop += size
  110. else
  111. null
  112. # @private
  113. #
  114. # @param row [Object] row data
  115. # @param sidx [Number] series index
  116. # @param type [String] "bar", "hover" or "label"
  117. colorFor: (row, sidx, type) ->
  118. if typeof @options.barColors is 'function'
  119. r = { x: row.x, y: row.y[sidx], label: row.label }
  120. s = { index: sidx, key: @options.ykeys[sidx], label: @options.labels[sidx] }
  121. @options.barColors.call(@, r, s, type)
  122. else
  123. @options.barColors[sidx % @options.barColors.length]
  124. # hit test - returns the index of the row at the given x-coordinate
  125. #
  126. hitTest: (x) ->
  127. return null if @data.length == 0
  128. x = Math.max(Math.min(x, @right), @left)
  129. Math.min(@data.length - 1,
  130. Math.floor((x - @left) / (@width / @data.length)))
  131. # click on grid event handler
  132. #
  133. # @private
  134. onGridClick: (x, y) =>
  135. index = @hitTest(x)
  136. @fire 'click', index, @data[index].src, x, y
  137. # hover movement event handler
  138. #
  139. # @private
  140. onHoverMove: (x, y) =>
  141. index = @hitTest(x)
  142. @hover.update(@hoverContentForRow(index)...)
  143. # hover out event handler
  144. #
  145. # @private
  146. onHoverOut: =>
  147. if @options.hideHover isnt false
  148. @hover.hide()
  149. # hover content for a point
  150. #
  151. # @private
  152. hoverContentForRow: (index) ->
  153. row = @data[index]
  154. content = "<div class='morris-hover-row-label'>#{row.label}</div>"
  155. for y, j in row.y
  156. content += """
  157. <div class='morris-hover-point' style='color: #{@colorFor(row, j, 'label')}'>
  158. #{@options.labels[j]}:
  159. #{@yLabelFormat(y)}
  160. </div>
  161. """
  162. if typeof @options.hoverCallback is 'function'
  163. content = @options.hoverCallback(index, @options, content, row.src)
  164. x = @left + (index + 0.5) * @width / @data.length
  165. [content, x]
  166. drawXAxisLabel: (xPos, yPos, text) ->
  167. label = @raphael.text(xPos, yPos, text)
  168. .attr('font-size', @options.gridTextSize)
  169. .attr('font-family', @options.gridTextFamily)
  170. .attr('font-weight', @options.gridTextWeight)
  171. .attr('fill', @options.gridTextColor)
  172. drawBar: (xPos, yPos, width, height, barColor, opacity, radiusArray) ->
  173. maxRadius = Math.max(radiusArray...)
  174. if maxRadius == 0 or maxRadius > height
  175. path = @raphael.rect(xPos, yPos, width, height)
  176. else
  177. path = @raphael.path @roundedRect(xPos, yPos, width, height, radiusArray)
  178. path
  179. .attr('fill', barColor)
  180. .attr('fill-opacity', opacity)
  181. .attr('stroke', 'none')
  182. roundedRect: (x, y, w, h, r = [0,0,0,0]) ->
  183. [ "M", x, r[0] + y, "Q", x, y, x + r[0], y,
  184. "L", x + w - r[1], y, "Q", x + w, y, x + w, y + r[1],
  185. "L", x + w, y + h - r[2], "Q", x + w, y + h, x + w - r[2], y + h,
  186. "L", x + r[3], y + h, "Q", x, y + h, x, y + h - r[3], "Z" ]