123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 |
- class Morris.Bar extends Morris.Grid
- constructor: (options) ->
- return new Morris.Bar(options) unless (@ instanceof Morris.Bar)
- super($.extend {}, options, parseTime: false)
- init: ->
- @cumulative = @options.stacked
- if @options.hideHover isnt 'always'
- @hover = new Morris.Hover(parent: @el)
- @on('hovermove', @onHoverMove)
- @on('hoverout', @onHoverOut)
- @on('gridclick', @onGridClick)
- # Default configuration
- #
- defaults:
- barSizeRatio: 0.75
- barGap: 3
- barColors: [
- '#0b62a4'
- '#7a92a3'
- '#4da74d'
- '#afd8f8'
- '#edc240'
- '#cb4b4b'
- '#9440ed'
- ],
- barOpacity: 1.0
- barRadius: [0, 0, 0, 0]
- xLabelMargin: 50
- # Do any size-related calculations
- #
- # @private
- calc: ->
- @calcBars()
- if @options.hideHover is false
- @hover.update(@hoverContentForRow(@data.length - 1)...)
- # calculate series data bars coordinates and sizes
- #
- # @private
- calcBars: ->
- for row, idx in @data
- row._x = @left + @width * (idx + 0.5) / @data.length
- row._y = for y in row.y
- if y? then @transY(y) else null
- # Draws the bar chart.
- #
- draw: ->
- @drawXAxis() if @options.axes in [true, 'both', 'x']
- @drawSeries()
- # draw the x-axis labels
- #
- # @private
- drawXAxis: ->
- # draw x axis labels
- ypos = @bottom + (@options.xAxisLabelTopPadding || @options.padding / 2)
- prevLabelMargin = null
- prevAngleMargin = null
- for i in [0...@data.length]
- row = @data[@data.length - 1 - i]
- label = @drawXAxisLabel(row._x, ypos, row.label)
- textBox = label.getBBox()
- label.transform("r#{-@options.xLabelAngle}")
- labelBox = label.getBBox()
- label.transform("t0,#{labelBox.height / 2}...")
- if @options.xLabelAngle != 0
- offset = -0.5 * textBox.width *
- Math.cos(@options.xLabelAngle * Math.PI / 180.0)
- label.transform("t#{offset},0...")
- # try to avoid overlaps
- if (not prevLabelMargin? or
- prevLabelMargin >= labelBox.x + labelBox.width or
- prevAngleMargin? and prevAngleMargin >= labelBox.x) and
- labelBox.x >= 0 and (labelBox.x + labelBox.width) < @el.width()
- if @options.xLabelAngle != 0
- margin = 1.25 * @options.gridTextSize /
- Math.sin(@options.xLabelAngle * Math.PI / 180.0)
- prevAngleMargin = labelBox.x - margin
- prevLabelMargin = labelBox.x - @options.xLabelMargin
- else
- label.remove()
- # draw the data series
- #
- # @private
- drawSeries: ->
- groupWidth = @width / @options.data.length
- numBars = if @options.stacked then 1 else @options.ykeys.length
- barWidth = (groupWidth * @options.barSizeRatio - @options.barGap * (numBars - 1)) / numBars
- barWidth = Math.min(barWidth, @options.barSize) if @options.barSize
- spaceLeft = groupWidth - barWidth * numBars - @options.barGap * (numBars - 1)
- leftPadding = spaceLeft / 2
- zeroPos = if @ymin <= 0 and @ymax >= 0 then @transY(0) else null
- @bars = for row, idx in @data
- lastTop = 0
- for ypos, sidx in row._y
- if ypos != null
- if zeroPos
- top = Math.min(ypos, zeroPos)
- bottom = Math.max(ypos, zeroPos)
- else
- top = ypos
- bottom = @bottom
- left = @left + idx * groupWidth + leftPadding
- left += sidx * (barWidth + @options.barGap) unless @options.stacked
- size = bottom - top
- if @options.verticalGridCondition and @options.verticalGridCondition(row.x)
- @drawBar(@left + idx * groupWidth, @top, groupWidth, Math.abs(@top - @bottom), @options.verticalGridColor, @options.verticalGridOpacity, @options.barRadius)
- top -= lastTop if @options.stacked
- @drawBar(left, top, barWidth, size, @colorFor(row, sidx, 'bar'),
- @options.barOpacity, @options.barRadius)
- lastTop += size
- else
- null
- # @private
- #
- # @param row [Object] row data
- # @param sidx [Number] series index
- # @param type [String] "bar", "hover" or "label"
- colorFor: (row, sidx, type) ->
- if typeof @options.barColors is 'function'
- r = { x: row.x, y: row.y[sidx], label: row.label }
- s = { index: sidx, key: @options.ykeys[sidx], label: @options.labels[sidx] }
- @options.barColors.call(@, r, s, type)
- else
- @options.barColors[sidx % @options.barColors.length]
- # hit test - returns the index of the row at the given x-coordinate
- #
- hitTest: (x) ->
- 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)))
- # click on grid event handler
- #
- # @private
- onGridClick: (x, y) =>
- index = @hitTest(x)
- @fire 'click', index, @data[index].src, x, y
- # hover movement event handler
- #
- # @private
- onHoverMove: (x, y) =>
- index = @hitTest(x)
- @hover.update(@hoverContentForRow(index)...)
- # hover out event handler
- #
- # @private
- onHoverOut: =>
- if @options.hideHover isnt false
- @hover.hide()
- # hover content for a point
- #
- # @private
- hoverContentForRow: (index) ->
- row = @data[index]
- content = "<div class='morris-hover-row-label'>#{row.label}</div>"
- for y, j in row.y
- content += """
- <div class='morris-hover-point' style='color: #{@colorFor(row, j, 'label')}'>
- #{@options.labels[j]}:
- #{@yLabelFormat(y)}
- </div>
- """
- if typeof @options.hoverCallback is 'function'
- content = @options.hoverCallback(index, @options, content, row.src)
- 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('font-family', @options.gridTextFamily)
- .attr('font-weight', @options.gridTextWeight)
- .attr('fill', @options.gridTextColor)
- drawBar: (xPos, yPos, width, height, barColor, opacity, radiusArray) ->
- maxRadius = Math.max(radiusArray...)
- if maxRadius == 0 or maxRadius > height
- path = @raphael.rect(xPos, yPos, width, height)
- else
- path = @raphael.path @roundedRect(xPos, yPos, width, height, radiusArray)
- path
- .attr('fill', barColor)
- .attr('fill-opacity', opacity)
- .attr('stroke', 'none')
- roundedRect: (x, y, w, h, r = [0,0,0,0]) ->
- [ "M", x, r[0] + y, "Q", x, y, x + r[0], y,
- "L", x + w - r[1], y, "Q", x + w, y, x + w, y + r[1],
- "L", x + w, y + h - r[2], "Q", x + w, y + h, x + w - r[2], y + h,
- "L", x + r[3], y + h, "Q", x, y + h, x, y + h - r[3], "Z" ]
|